mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 23:40:24 +08:00
refactor: 消息模块重构,infra包路径简写等
This commit is contained in:
@@ -8,3 +8,7 @@ VITE_OPEN = false
|
||||
VITE_PUBLIC_PATH = ''
|
||||
|
||||
VITE_EDITOR=idea
|
||||
|
||||
# 路由模式
|
||||
# Optional: hash | history
|
||||
VITE_ROUTER_MODE = hash
|
||||
@@ -5,7 +5,3 @@ VITE_OPEN = true
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = '/api'
|
||||
|
||||
# 路由模式
|
||||
# Optional: hash | history
|
||||
VITE_ROUTER_MODE = hash
|
||||
@@ -3,7 +3,3 @@ ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = '/api'
|
||||
|
||||
# 路由模式
|
||||
# Optional: hash | history
|
||||
VITE_ROUTER_MODE = hash
|
||||
@@ -24,7 +24,7 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "^2.10.3",
|
||||
"element-plus": "^2.10.4",
|
||||
"js-base64": "^3.7.7",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"mitt": "^3.0.1",
|
||||
@@ -39,8 +39,8 @@
|
||||
"sql-formatter": "^15.6.5",
|
||||
"trzsz": "^1.1.5",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.5.17",
|
||||
"vue-i18n": "^11.1.9",
|
||||
"vue": "^v3.6.0-alpha.2",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
@@ -56,7 +56,6 @@
|
||||
"@vue/compiler-sfc": "^3.5.17",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"code-inspector-plugin": "^0.20.12",
|
||||
"dotenv": "^16.5.0",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-vue": "^10.2.0",
|
||||
"postcss": "^8.5.6",
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
class SocketBuilder {
|
||||
websocket: WebSocket;
|
||||
|
||||
constructor(url: string) {
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
throw new Error('不支持websocket');
|
||||
}
|
||||
if (!url) {
|
||||
throw new Error('websocket url不能为空');
|
||||
}
|
||||
this.websocket = new WebSocket(url);
|
||||
}
|
||||
|
||||
static builder(url: string) {
|
||||
return new SocketBuilder(url);
|
||||
}
|
||||
|
||||
open(onopen: any) {
|
||||
this.websocket.onopen = onopen;
|
||||
return this;
|
||||
}
|
||||
|
||||
error(onerror: any) {
|
||||
this.websocket.onerror = onerror;
|
||||
return this;
|
||||
}
|
||||
|
||||
message(onmessage: any) {
|
||||
this.websocket.onmessage = onmessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
close(onclose: any) {
|
||||
this.websocket.onclose = onclose;
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
return this.websocket;
|
||||
}
|
||||
}
|
||||
|
||||
export default SocketBuilder;
|
||||
@@ -42,3 +42,27 @@ export const TagResourceTypePath = {
|
||||
Db: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}/${TagResourceTypeEnum.Db.value}`,
|
||||
Es: `${TagResourceTypeEnum.EsInstance.value}/${TagResourceTypeEnum.AuthCert.value}`,
|
||||
};
|
||||
|
||||
// 消息子类型
|
||||
export const MsgSubtypeEnum = {
|
||||
UserLogin: EnumValue.of('user.login', 'login.login').setExtra({
|
||||
notifyType: 'primary',
|
||||
}),
|
||||
|
||||
MachineFileUploadSuccess: EnumValue.of('machine.file.upload.success', 'machine.fileUploadSuccess').setExtra({
|
||||
notifyType: 'success',
|
||||
}),
|
||||
MachineFileUploadFail: EnumValue.of('machine.file.upload.fail', 'machine.fileUploadFail').setExtra({
|
||||
notifyType: 'error',
|
||||
}),
|
||||
|
||||
DbDumpFail: EnumValue.of('db.dump.fail', 'db.dbDumpFail').setExtra({
|
||||
notifyType: 'error',
|
||||
}),
|
||||
SqlScriptRunSuccess: EnumValue.of('db.sqlscript.run.success', 'db.sqlScriptRunSuccess').setExtra({
|
||||
notifyType: 'success',
|
||||
}),
|
||||
SqlScriptRunFail: EnumValue.of('db.sqlscript.run.fail', 'db.sqlScriptRunFail').setExtra({
|
||||
notifyType: 'error',
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import openApi from './openApi';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
import { notBlank } from './assert';
|
||||
|
||||
/**
|
||||
* AES 加密数据
|
||||
@@ -36,3 +39,36 @@ export function AesDecrypt(word: string, key?: string): string {
|
||||
|
||||
return decrypted.toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
var encryptor: any = null;
|
||||
|
||||
export async function getRsaPublicKey() {
|
||||
let publicKey = sessionStorage.getItem('RsaPublicKey');
|
||||
if (publicKey) {
|
||||
return publicKey;
|
||||
}
|
||||
publicKey = (await openApi.getPublicKey()) as string;
|
||||
sessionStorage.setItem('RsaPublicKey', publicKey);
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥加密指定值
|
||||
*
|
||||
* @param value value
|
||||
* @returns 加密后的值
|
||||
*/
|
||||
export async function RsaEncrypt(value: any) {
|
||||
// 不存在则返回空值
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
if (encryptor != null && sessionStorage.getItem('RsaPublicKey') != null) {
|
||||
return encryptor.encrypt(value);
|
||||
}
|
||||
encryptor = new JSEncrypt();
|
||||
const publicKey = (await getRsaPublicKey()) as string;
|
||||
notBlank(publicKey, '获取公钥失败');
|
||||
encryptor.setPublicKey(publicKey); //设置公钥
|
||||
return encryptor.encrypt(value);
|
||||
}
|
||||
|
||||
@@ -204,6 +204,24 @@ function getApiUrl(url: string) {
|
||||
return baseUrl + url + '?' + joinClientParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 websocket
|
||||
*/
|
||||
export const createWebSocket = (url: string): Promise<WebSocket> => {
|
||||
return new Promise<WebSocket>((resolve, reject) => {
|
||||
const clientParam = (url.includes('?') ? '&' : '?') + joinClientParams();
|
||||
const socket = new WebSocket(`${config.baseWsUrl}${url}${clientParam}`);
|
||||
|
||||
socket.onopen = () => {
|
||||
resolve(socket);
|
||||
};
|
||||
|
||||
socket.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 组装客户端参数,包括 token 和 clientId
|
||||
export function joinClientParams(): string {
|
||||
return `token=${getToken()}&clientId=${getClientId()}`;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import openApi from './openApi';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
import { notBlank } from './assert';
|
||||
|
||||
var encryptor: any = null;
|
||||
|
||||
export async function getRsaPublicKey() {
|
||||
let publicKey = sessionStorage.getItem('RsaPublicKey');
|
||||
if (publicKey) {
|
||||
return publicKey;
|
||||
}
|
||||
publicKey = (await openApi.getPublicKey()) as string;
|
||||
sessionStorage.setItem('RsaPublicKey', publicKey);
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥加密指定值
|
||||
*
|
||||
* @param value value
|
||||
* @returns 加密后的值
|
||||
*/
|
||||
export async function RsaEncrypt(value: any) {
|
||||
// 不存在则返回空值
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
if (encryptor != null && sessionStorage.getItem('RsaPublicKey') != null) {
|
||||
return encryptor.encrypt(value);
|
||||
}
|
||||
encryptor = new JSEncrypt();
|
||||
const publicKey = (await getRsaPublicKey()) as string;
|
||||
notBlank(publicKey, '获取公钥失败');
|
||||
encryptor.setPublicKey(publicKey); //设置公钥
|
||||
return encryptor.encrypt(value);
|
||||
}
|
||||
@@ -4,15 +4,15 @@ import { h, reactive } from 'vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
|
||||
|
||||
export function initSysMsgs() {
|
||||
registerDbSqlExecProgress();
|
||||
export async function initSysMsgs() {
|
||||
await registerDbSqlExecProgress();
|
||||
}
|
||||
|
||||
const sqlExecNotifyMap: Map<string, any> = new Map();
|
||||
|
||||
function registerDbSqlExecProgress() {
|
||||
syssocket.registerMsgHandler('execSqlFileProgress', function (message: any) {
|
||||
const content = JSON.parse(message.msg);
|
||||
async function registerDbSqlExecProgress() {
|
||||
await syssocket.registerMsgHandler('sqlScriptRunProgress', function (message: any) {
|
||||
const content = message.params;
|
||||
const id = content.id;
|
||||
let progress = sqlExecNotifyMap.get(id);
|
||||
if (content.terminated) {
|
||||
@@ -38,7 +38,7 @@ function registerDbSqlExecProgress() {
|
||||
duration: 0,
|
||||
title: message.title,
|
||||
message: h(ProgressNotify, progress.props),
|
||||
type: syssocket.getMsgType(message.type),
|
||||
type: 'info',
|
||||
showClose: false,
|
||||
});
|
||||
sqlExecNotifyMap.set(id, progress);
|
||||
|
||||
@@ -1,34 +1,25 @@
|
||||
import Config from './config';
|
||||
import SocketBuilder from './SocketBuilder';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
|
||||
import { joinClientParams } from './request';
|
||||
import { createWebSocket } from './request';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { MsgSubtypeEnum } from './commonEnum';
|
||||
import EnumValue from './Enum';
|
||||
|
||||
class SysSocket {
|
||||
/**
|
||||
* socket连接
|
||||
*/
|
||||
socket: any;
|
||||
socket: WebSocket | null = null;
|
||||
|
||||
/**
|
||||
* key -> 消息类别,value -> 消息对应的处理器函数
|
||||
*/
|
||||
categoryHandlers: Map<string, any> = new Map();
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
messageTypes: any = {
|
||||
0: 'error',
|
||||
1: 'success',
|
||||
2: 'info',
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化全局系统消息websocket
|
||||
*/
|
||||
init() {
|
||||
async init() {
|
||||
// 存在则不需要重新建立连接
|
||||
if (this.socket) {
|
||||
return;
|
||||
@@ -38,9 +29,9 @@ class SysSocket {
|
||||
return null;
|
||||
}
|
||||
console.log('init system ws');
|
||||
const sysMsgUrl = `${Config.baseWsUrl}/sysmsg?${joinClientParams()}`;
|
||||
this.socket = SocketBuilder.builder(sysMsgUrl)
|
||||
.message((event: { data: string }) => {
|
||||
try {
|
||||
this.socket = await createWebSocket('/sysmsg');
|
||||
this.socket.onmessage = async (event: { data: string }) => {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
@@ -56,23 +47,32 @@ class SysSocket {
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认通知处理
|
||||
const type = this.getMsgType(message.type);
|
||||
let msg = message.msg;
|
||||
let duration = 0;
|
||||
const msgSubtype = EnumValue.getEnumByValue(MsgSubtypeEnum, message.subtype);
|
||||
if (!msgSubtype) {
|
||||
console.log(`not found msg subtype: ${message.subtype}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态导入 i18n 或延迟获取 i18n 实例
|
||||
let title = '';
|
||||
try {
|
||||
// 方式1: 动态导入
|
||||
const { i18n } = await import('@/i18n');
|
||||
title = i18n.global.t(msgSubtype?.label);
|
||||
} catch (e) {
|
||||
console.warn('i18n not ready, using default title');
|
||||
}
|
||||
|
||||
ElNotification({
|
||||
duration: duration,
|
||||
title: message.title,
|
||||
message: msg,
|
||||
type: type,
|
||||
duration: 0,
|
||||
title,
|
||||
message: message.msg,
|
||||
type: msgSubtype?.extra.notifyType || 'info',
|
||||
});
|
||||
})
|
||||
.open((event: any) => console.log(event))
|
||||
.close(() => {
|
||||
console.log('close sys socket');
|
||||
this.socket = null;
|
||||
})
|
||||
.build();
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('open system ws error', e);
|
||||
}
|
||||
}
|
||||
|
||||
destory() {
|
||||
@@ -87,8 +87,7 @@ class SysSocket {
|
||||
* @param category 消息类别
|
||||
* @param handlerFunc 消息处理函数
|
||||
*/
|
||||
registerMsgHandler(category: any, handlerFunc: any) {
|
||||
this.init();
|
||||
async registerMsgHandler(category: any, handlerFunc: any) {
|
||||
if (this.categoryHandlers.has(category)) {
|
||||
console.log(`${category}该类别消息处理器已存在...`);
|
||||
return;
|
||||
@@ -98,10 +97,6 @@ class SysSocket {
|
||||
}
|
||||
this.categoryHandlers.set(category, handlerFunc);
|
||||
}
|
||||
|
||||
getMsgType(msgType: any) {
|
||||
return this.messageTypes[msgType];
|
||||
}
|
||||
}
|
||||
|
||||
// 全局系统消息websocket;
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
/**
|
||||
* 2020.11.29 lyt 整理
|
||||
* 工具类集合,适用于平时开发
|
||||
*/
|
||||
|
||||
// 小数或整数(不可以负数)
|
||||
export function verifyNumberIntegerAndFloat(val: string) {
|
||||
// 匹配空格
|
||||
let v = val.replace(/(^\s*)|(\s*$)/g, '');
|
||||
// 只能是数字和小数点,不能是其他输入
|
||||
v = v.replace(/[^\d.]/g, '');
|
||||
// 以0开始只能输入一个
|
||||
v = v.replace(/^0{2}$/g, '0');
|
||||
// 保证第一位只能是数字,不能是点
|
||||
v = v.replace(/^\./g, '');
|
||||
// 小数只能出现1位
|
||||
v = v.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.');
|
||||
// 小数点后面保留2位
|
||||
v = v.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 正整数验证
|
||||
export function verifiyNumberInteger(val: string) {
|
||||
// 匹配空格
|
||||
let v = val.replace(/(^\s*)|(\s*$)/g, '');
|
||||
// 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
|
||||
v = v.replace(/[\.]*/g, '');
|
||||
// 去掉以 0 开始后面的数, 防止贴贴的时候出现问题 如 00121323
|
||||
v = v.replace(/(^0[\d]*)$/g, '0');
|
||||
// 首位是0,只能出现一次
|
||||
v = v.replace(/^0\d$/g, '0');
|
||||
// 只匹配数字
|
||||
v = v.replace(/[^\d]/g, '');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 去掉中文及空格
|
||||
export function verifyCnAndSpace(val: string) {
|
||||
// 匹配中文与空格
|
||||
let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '');
|
||||
// 匹配空格
|
||||
v = v.replace(/(^\s*)|(\s*$)/g, '');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 去掉英文及空格
|
||||
export function verifyEnAndSpace(val: string) {
|
||||
// 匹配英文与空格
|
||||
let v = val.replace(/[a-zA-Z]+/g, '');
|
||||
// 匹配空格
|
||||
v = v.replace(/(^\s*)|(\s*$)/g, '');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 禁止输入空格
|
||||
export function verifyAndSpace(val: string) {
|
||||
// 匹配空格
|
||||
let v = val.replace(/(^\s*)|(\s*$)/g, '');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 金额用 `,` 区分开
|
||||
export function verifyNumberComma(val: string) {
|
||||
// 调用小数或整数(不可以负数)方法
|
||||
let v: any = verifyNumberIntegerAndFloat(val);
|
||||
// 字符串转成数组
|
||||
v = v.toString().split('.');
|
||||
// \B 匹配非单词边界,两边都是单词字符或者两边都是非单词字符
|
||||
v[0] = v[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
// 数组转字符串
|
||||
v = v.join('.');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 匹配文字变色(搜索时)
|
||||
export function verifyTextColor(val: string, text = '', color = 'red') {
|
||||
// 返回内容,添加颜色
|
||||
let v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`);
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 数字转中文大写
|
||||
export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾万仟佰拾元角分', v = '') {
|
||||
// 当前内容字符串添加 2个0,为什么??
|
||||
val += '00';
|
||||
// 返回某个指定的字符串值在字符串中首次出现的位置,没有出现,则该方法返回 -1
|
||||
let lookup = val.indexOf('.');
|
||||
// substring:不包含结束下标内容,substr:包含结束下标内容
|
||||
if (lookup >= 0) val = val.substring(0, lookup) + val.substr(lookup + 1, 2);
|
||||
// 根据内容 val 的长度,截取返回对应大写
|
||||
unit = unit.substr(unit.length - val.length);
|
||||
// 循环截取拼接大写
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
v += '零壹贰叁肆伍陆柒捌玖'.substr(val.substr(i, 1), 1) + unit.substr(i, 1);
|
||||
}
|
||||
// 正则处理
|
||||
v = v
|
||||
.replace(/零角零分$/, '整')
|
||||
.replace(/零[仟佰拾]/g, '零')
|
||||
.replace(/零{2,}/g, '零')
|
||||
.replace(/零([亿|万])/g, '$1')
|
||||
.replace(/零+元/, '元')
|
||||
.replace(/亿零{0,3}万/, '亿')
|
||||
.replace(/^元/, '零元');
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// 手机号码
|
||||
export function verifyPhone(val: string) {
|
||||
// false: 手机号码不正确
|
||||
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(val)) return false;
|
||||
// true: 手机号码正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 国内电话号码
|
||||
export function verifyTelPhone(val: string) {
|
||||
// false: 国内电话号码不正确
|
||||
if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false;
|
||||
// true: 国内电话号码正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 登录账号 (字母开头,允许5-16字节,允许字母数字下划线)
|
||||
export function verifyAccount(val: string) {
|
||||
// false: 登录账号不正确
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false;
|
||||
// true: 登录账号正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 密码 (以字母开头,长度在6~16之间,只能包含字母、数字和下划线)
|
||||
export function verifyPassword(val: string) {
|
||||
// false: 密码不正确
|
||||
if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false;
|
||||
// true: 密码正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 强密码 (字母+数字+特殊字符,长度在6-16之间)
|
||||
export function verifyPasswordPowerful(val: string) {
|
||||
// false: 强密码不正确
|
||||
if (!/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
|
||||
return false;
|
||||
// true: 强密码正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 密码强度
|
||||
export function verifyPasswordStrength(val: string) {
|
||||
let v = '';
|
||||
// 弱:纯数字,纯字母,纯特殊字符
|
||||
if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,16}$/.test(val)) v = '弱';
|
||||
// 中:字母+数字,字母+特殊字符,数字+特殊字符
|
||||
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中';
|
||||
// 强:字母+数字+特殊字符
|
||||
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '强';
|
||||
// 返回结果
|
||||
return v;
|
||||
}
|
||||
|
||||
// IP地址
|
||||
export function verifyIPAddress(val: string) {
|
||||
// false: IP地址不正确
|
||||
if (!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(val))
|
||||
return false;
|
||||
// true: IP地址正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 邮箱
|
||||
export function verifyEmail(val: string) {
|
||||
// false: 邮箱不正确
|
||||
if (
|
||||
!/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
|
||||
val
|
||||
)
|
||||
)
|
||||
return false;
|
||||
// true: 邮箱正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 身份证
|
||||
export function verifyIdCard(val: string) {
|
||||
// false: 身份证不正确
|
||||
if (!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(val)) return false;
|
||||
// true: 身份证正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 姓名
|
||||
export function verifyFullName(val: string) {
|
||||
// false: 姓名不正确
|
||||
if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val)) return false;
|
||||
// true: 姓名正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 邮政编码
|
||||
export function verifyPostalCode(val: string) {
|
||||
// false: 邮政编码不正确
|
||||
if (!/^[1-9][0-9]{5}$/.test(val)) return false;
|
||||
// true: 邮政编码正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// url
|
||||
export function verifyUrl(val: string) {
|
||||
// false: url不正确
|
||||
if (
|
||||
!/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
|
||||
val
|
||||
)
|
||||
)
|
||||
return false;
|
||||
// true: url正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
// 车牌号
|
||||
export function verifyCarNum(val: string) {
|
||||
// false: 车牌号不正确
|
||||
if (
|
||||
!/^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(
|
||||
val
|
||||
)
|
||||
)
|
||||
return false;
|
||||
// true:车牌号正确
|
||||
else return true;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
const mode = import.meta.env.VITE_ROUTER_MODE;
|
||||
|
||||
/**
|
||||
* @description 获取不同路由模式所对应的 url
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getNowUrl() {
|
||||
const url = {
|
||||
hash: location.hash.substring(1),
|
||||
history: location.pathname + location.search,
|
||||
};
|
||||
return url[mode];
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// vite 打包相关
|
||||
import dotenv from 'dotenv';
|
||||
export interface ViteEnv {
|
||||
VITE_PORT: number;
|
||||
VITE_OPEN: boolean;
|
||||
VITE_PUBLIC_PATH: string;
|
||||
VITE_EDITOR: string;
|
||||
}
|
||||
|
||||
export function loadEnv(): ViteEnv {
|
||||
const env = process.env.NODE_ENV;
|
||||
const ret: any = {};
|
||||
const envList = [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env', ,];
|
||||
envList.forEach((e) => {
|
||||
dotenv.config({ path: e });
|
||||
});
|
||||
for (const envName of Object.keys(process.env)) {
|
||||
console.log(envName);
|
||||
let realName = (process.env as any)[envName].replace(/\\n/g, '\n');
|
||||
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
|
||||
if (envName === 'VITE_PORT') realName = Number(realName);
|
||||
if (envName === 'VITE_OPEN') realName = Boolean(realName);
|
||||
ret[envName] = realName;
|
||||
process.env[envName] = realName;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="flex">
|
||||
<!-- 简易单个搜索项 -->
|
||||
<div v-if="nowSearchItem" class="flex">
|
||||
<el-dropdown v-if="searchItems?.length > 1">
|
||||
<el-dropdown v-if="props.searchItems?.length > 1">
|
||||
<SvgIcon :size="16" name="CaretBottom" class="!mr-1 !mt-1.5 simple-search-form-btn" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
@@ -54,7 +54,7 @@
|
||||
<!-- <el-button v-if="showToolButton('refresh')" icon="Refresh" circle @click="execQuery()" /> -->
|
||||
|
||||
<el-button
|
||||
v-if="showToolButton('search') && searchItems?.length > 1"
|
||||
v-if="showToolButton('search') && props.searchItems?.length > 1"
|
||||
:icon="isShowSearch ? 'ArrowDown' : 'ArrowUp'"
|
||||
circle
|
||||
@click="isShowSearch = !isShowSearch"
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useDebounceFn, useEventListener, useIntervalFn } from '@vueuse/core';
|
||||
import themes from './themes';
|
||||
import { TrzszFilter } from 'trzsz';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { createWebSocket } from '@/common/request';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -124,7 +125,7 @@ const initTerm = async () => {
|
||||
// 注册窗口大小监听器
|
||||
useEventListener('resize', useDebounceFn(fitTerminal, 400));
|
||||
|
||||
initSocket();
|
||||
await initSocket();
|
||||
// 注册其他插件
|
||||
loadAddon();
|
||||
|
||||
@@ -140,13 +141,19 @@ const initTerm = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
const initSocket = () => {
|
||||
const initSocket = async () => {
|
||||
if (!props.socketUrl) {
|
||||
return;
|
||||
}
|
||||
socket = new WebSocket(`${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`);
|
||||
// 监听socket连接
|
||||
socket.onopen = () => {
|
||||
try {
|
||||
socket = await createWebSocket(`${props.socketUrl}?rows=${term?.rows}&cols=${term?.cols}`);
|
||||
} catch (e) {
|
||||
term.writeln(`\r\n\x1b[31m${t('components.terminal.connErrMsg')}`);
|
||||
state.status = TerminalStatus.Error;
|
||||
console.log('连接错误', e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册心跳
|
||||
useIntervalFn(sendPing, 15000);
|
||||
|
||||
@@ -159,14 +166,6 @@ const initSocket = () => {
|
||||
if (props.cmd) {
|
||||
sendData(props.cmd + ' \r');
|
||||
}
|
||||
};
|
||||
|
||||
// 监听socket错误信息
|
||||
socket.onerror = (e: Event) => {
|
||||
term.writeln(`\r\n\x1b[31m${t('components.terminal.connErrMsg')}`);
|
||||
state.status = TerminalStatus.Error;
|
||||
console.log('连接错误', e);
|
||||
};
|
||||
|
||||
socket.onclose = (e: CloseEvent) => {
|
||||
console.log('terminal socket close...', e.reason);
|
||||
|
||||
@@ -16,6 +16,7 @@ export default {
|
||||
dbFilterPlaceholder: 'DB name: Input filterable',
|
||||
sqlRecord: 'SQL records',
|
||||
dump: 'Export',
|
||||
dbDumpFail: 'DB export failed',
|
||||
dumpContent: 'Export Content',
|
||||
structure: 'Structure',
|
||||
data: 'Data',
|
||||
@@ -55,6 +56,8 @@ export default {
|
||||
execSuccess: 'Successful execution',
|
||||
execFail: 'Execution failure',
|
||||
sqlScriptRun: 'Run SQL Script',
|
||||
sqlScriptRunSuccess: 'SQL script executed successfully',
|
||||
sqlScriptRunFail: 'SQL script execution failed',
|
||||
saveSql: 'Save SQL',
|
||||
execInfo: 'Execution info',
|
||||
result: 'Result',
|
||||
|
||||
@@ -138,5 +138,7 @@ export default {
|
||||
fileTooLargeTips: 'The file is too large, please download and use it',
|
||||
uploadSuccess: 'Upload successfully',
|
||||
fileExceedsSysConf: 'The uploaded file exceeds the system configuration [{uploadMaxFileSize}]',
|
||||
fileUploadSuccess: 'Machine file upload successful',
|
||||
fileUploadFail: 'Machine file upload failed',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ export default {
|
||||
dbFilterPlaceholder: '库名: 输入可过滤',
|
||||
sqlRecord: 'SQL记录',
|
||||
dump: '导出',
|
||||
dbDumpFail: '数据库导出失败',
|
||||
dumpContent: '导出内容',
|
||||
structure: '结构',
|
||||
data: '数据',
|
||||
@@ -55,6 +56,8 @@ export default {
|
||||
execSuccess: '执行成功',
|
||||
execFail: '执行失败',
|
||||
sqlScriptRun: 'SQL脚本执行',
|
||||
sqlScriptRunSuccess: 'SQL脚本执行成功',
|
||||
sqlScriptRunFail: 'SQL脚本执行失败',
|
||||
saveSql: '保存SQL',
|
||||
execInfo: '执行信息',
|
||||
result: '结果',
|
||||
|
||||
@@ -139,5 +139,7 @@ export default {
|
||||
fileTooLargeTips: '文件太大, 请下载使用',
|
||||
uploadSuccess: '上传成功',
|
||||
fileExceedsSysConf: '上传的文件超过系统配置的【{uploadMaxFileSize}】',
|
||||
fileUploadSuccess: '机器文件上传成功',
|
||||
fileUploadFail: '机器文件上传失败',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -41,25 +41,27 @@
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||
<SvgIcon name="search" :title="$t('layout.user.menuSearch')" />
|
||||
</div>
|
||||
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
|
||||
<SvgIcon name="setting" :title="$t('layout.user.layoutConf')" />
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<el-popover placement="bottom" trigger="click" :visible="state.isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
|
||||
|
||||
<el-popover @show="onShowMsgs" placement="bottom" trigger="click" :width="450">
|
||||
<template #reference>
|
||||
<el-badge :is-dot="false" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<el-badge :show-zero="false" :value="state.unreadMsgCount">
|
||||
<SvgIcon name="bell" :title="$t('layout.user.news')" />
|
||||
</el-badge>
|
||||
</template>
|
||||
<transition name="el-zoom-in-top">
|
||||
<UserNews v-show="state.isShowUserNewsPopover" />
|
||||
</transition>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
<UserNews ref="userNewsRef" @update:count="state.unreadMsgCount = $event" />
|
||||
</el-popover>
|
||||
|
||||
<div class="layout-navbars-breadcrumb-user-icon mr-2" @click="onScreenfullClick">
|
||||
<SvgIcon v-if="!state.isScreenfull" name="full-screen" :title="$t('layout.user.fullScreenOff')" />
|
||||
<SvgIcon v-else name="crop" />
|
||||
</div>
|
||||
|
||||
<el-dropdown trigger="click" :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link cursor-pointer">
|
||||
<img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr-1" />
|
||||
@@ -79,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||
import { ref, computed, reactive, onMounted, watch } from 'vue';
|
||||
import { ref, computed, reactive, onMounted, watch, useTemplateRef } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
@@ -100,10 +102,12 @@ import EnumValue from '@/common/Enum';
|
||||
|
||||
const router = useRouter();
|
||||
const searchRef = ref();
|
||||
const userNewsRef = useTemplateRef('userNewsRef');
|
||||
|
||||
const state = reactive({
|
||||
isScreenfull: false,
|
||||
isShowUserNewsPopover: false,
|
||||
disabledSize: '',
|
||||
unreadMsgCount: 0,
|
||||
});
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
const themeConfigStore = useThemeConfig();
|
||||
@@ -126,8 +130,15 @@ onMounted(() => {
|
||||
initComponentSize();
|
||||
isDark.value = themeConfig.isDark;
|
||||
}
|
||||
|
||||
// 获取未读消息数量
|
||||
state.unreadMsgCount = 0;
|
||||
});
|
||||
|
||||
const onShowMsgs = () => {
|
||||
userNewsRef.value?.loadMsgs(true);
|
||||
};
|
||||
|
||||
// 全屏点击时
|
||||
const onScreenfullClick = () => {
|
||||
if (!screenfull.isEnabled) {
|
||||
|
||||
@@ -1,117 +1,132 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb-user-news">
|
||||
<div class="head-box">
|
||||
<div class="head-box-title">{{ $t('layout.user.newTitle') }}</div>
|
||||
<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('layout.user.newBtn') }}</div>
|
||||
<div class="flex flex-col w-full rounded-md shadow-sm">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-gray-200 px-4 py-2 text-sm text-gray-700">
|
||||
<div class="font-medium">{{ $t('layout.user.newTitle') }}</div>
|
||||
<div v-if="unreadCount > 0" class="color-primary cursor-pointer opacity-80 transition-opacity hover:opacity-100" @click="onRead()">
|
||||
{{ $t('layout.user.newBtn') }}
|
||||
</div>
|
||||
<div class="content-box">
|
||||
<template v-if="newsList.length > 0">
|
||||
<div class="content-box-item" v-for="(v, k) in newsList" :key="k">
|
||||
<div>{{ v.label }}</div>
|
||||
<div class="content-box-msg">
|
||||
{{ v.value }}
|
||||
</div>
|
||||
<div class="content-box-time">{{ v.time }}</div>
|
||||
|
||||
<!-- Content -->
|
||||
<el-scrollbar height="350px" v-loading="loadingMsgs" class="px-4 py-2 text-sm">
|
||||
<template v-if="msgs.length > 0">
|
||||
<div
|
||||
v-for="(v, k) in msgs"
|
||||
:key="k"
|
||||
class="pt-1 mt-0.5"
|
||||
:style="{ backgroundColor: v.status == 1 ? 'var(--el-color-info-light-9)' : 'transparent' }"
|
||||
@click="onRead(v)"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<el-text size="small" tag="b" :type="EnumValue.getEnumByValue(MsgSubtypeEnum, v.subtype)?.extra?.notifyType">
|
||||
{{ $t(EnumValue.getEnumByValue(MsgSubtypeEnum, v.subtype)?.label || '') }}
|
||||
</el-text>
|
||||
</div>
|
||||
<div class="text-gray-500 mt-1 mb-1">{{ v.msg }}</div>
|
||||
<div class="text-gray-500">{{ formatDate(v.createTime) }}</div>
|
||||
<div class="mt-2 border-t border-gray-200"></div>
|
||||
</div>
|
||||
|
||||
<el-button class="w-full mt-1" size="small" @click="loadMsgs()" v-if="!loadMoreDisable"> {{ $t('redis.loadMore') }} </el-button>
|
||||
</template>
|
||||
<el-empty :description="$t('layout.user.newDesc')" v-else></el-empty>
|
||||
</div>
|
||||
<div class="foot-box" @click="toMsgCenter" v-if="newsList.length > 0">{{ $t('layout.user.newGo') }}</div>
|
||||
|
||||
<el-empty v-if="msgs.length == 0 && !loadingMsgs" :image-size="100" :description="$t('layout.user.newDesc')" />
|
||||
</el-scrollbar>
|
||||
|
||||
<!-- Footer -->
|
||||
<!-- <div
|
||||
v-if="msgs.length > 0"
|
||||
class="color-primary flex h-9 items-center justify-center border-t border-gray-200 text-sm cursor-pointer opacity-80 transition-opacity hover:opacity-100"
|
||||
@click="toMsgCenter"
|
||||
>
|
||||
{{ $t('layout.user.newGo') }}
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs } from 'vue';
|
||||
export default {
|
||||
name: 'layoutBreadcrumbUserNews',
|
||||
setup() {
|
||||
const state = reactive({
|
||||
newsList: [
|
||||
{
|
||||
label: '关于学习交流的通知',
|
||||
value: 'QQ群号码 119699946',
|
||||
time: '2021-09-08',
|
||||
},
|
||||
],
|
||||
<script lang="ts" setup>
|
||||
import { MsgSubtypeEnum } from '@/common/commonEnum';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { personApi } from '@/views/personal/api';
|
||||
import { useIntervalFn } from '@vueuse/core';
|
||||
import { onMounted, ref, watchEffect } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update:count']);
|
||||
|
||||
const msgQuery = ref<any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const loadMoreDisable = ref(true);
|
||||
const loadingMsgs = ref(true);
|
||||
const msgs = ref<Array<any>>([]);
|
||||
const unreadCount = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
useIntervalFn(
|
||||
() => {
|
||||
// 定时更新未读消息数
|
||||
personApi.getUnreadMsgCount.request().then((res) => {
|
||||
unreadCount.value = res;
|
||||
});
|
||||
// 全部已读点击
|
||||
const onAllReadClick = () => {
|
||||
state.newsList = [];
|
||||
};
|
||||
// 前往通知中心点击
|
||||
const toMsgCenter = () => {};
|
||||
return {
|
||||
onAllReadClick,
|
||||
toMsgCenter,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
10 * 1000,
|
||||
{ immediate: true, immediateCallback: true }
|
||||
);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:count', unreadCount.value);
|
||||
});
|
||||
|
||||
const loadMsgs = async (research: boolean = false) => {
|
||||
if (research) {
|
||||
msgQuery.value.pageNum = 1;
|
||||
msgs.value = [];
|
||||
}
|
||||
const msgList = await getMsgs();
|
||||
msgs.value.push(...msgList.list);
|
||||
msgQuery.value.pageNum += 1;
|
||||
|
||||
loadMoreDisable.value = msgList.total <= msgs.value.length;
|
||||
};
|
||||
|
||||
const getMsgs = async () => {
|
||||
try {
|
||||
loadingMsgs.value = true;
|
||||
return await personApi.getMsgs.request(msgQuery.value);
|
||||
} catch (e) {
|
||||
//
|
||||
} finally {
|
||||
loadingMsgs.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onRead = async (msg: any = null) => {
|
||||
if (msg && (msg.status == 1 || !msg.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await personApi.readMsg.request({ id: msg?.id || 0 });
|
||||
loadMsgs(true);
|
||||
|
||||
if (!msg) {
|
||||
// 如果是全部已读,重置未读消息数
|
||||
unreadCount.value = 0;
|
||||
} else {
|
||||
// 如果是单条已读,减少未读消息数
|
||||
unreadCount.value = Math.max(unreadCount.value - 1, 0);
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
loadMsgs,
|
||||
});
|
||||
|
||||
const toMsgCenter = () => {};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-breadcrumb-user-news {
|
||||
.head-box {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
box-sizing: border-box;
|
||||
color: #333333;
|
||||
justify-content: space-between;
|
||||
height: 35px;
|
||||
align-items: center;
|
||||
|
||||
.head-box-btn {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-box {
|
||||
font-size: 13px;
|
||||
|
||||
.content-box-item {
|
||||
padding-top: 12px;
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.content-box-msg {
|
||||
color: #999999;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.content-box-time {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.foot-box {
|
||||
height: 35px;
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #ebeef5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-empty__description p) {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getNowUrl } from '@/common/utils/url';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**
|
||||
@@ -12,14 +11,5 @@ export const useTagsViews = defineStore('tagsViews', {
|
||||
setTagsViews(data: Array<TagsView>) {
|
||||
this.tagsViews = data;
|
||||
},
|
||||
// 设置当前页面的tags view title
|
||||
setNowTitle(title: string) {
|
||||
this.tagsViews.forEach((item) => {
|
||||
// console.log(getNowUrl(), item.path);
|
||||
if (item.path == getNowUrl()) {
|
||||
item.title = title;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="home-container personal">
|
||||
<el-row :gutter="15">
|
||||
<!-- 个人信息 -->
|
||||
<el-col :xs="24" :sm="16">
|
||||
<el-col :xs="24" :sm="24">
|
||||
<el-card shadow="hover" :header="$t('home.personalInfo')">
|
||||
<div class="personal-user">
|
||||
<div class="personal-user-left">
|
||||
@@ -52,23 +52,6 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<el-col :xs="24" :sm="8" class="pl15 personal-info">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<span>{{ $t('home.msgNotify') }}</span>
|
||||
<span @click="showMsgs" class="personal-info-more">{{ $t('common.more') }}</span>
|
||||
</template>
|
||||
<div class="personal-info-box">
|
||||
<ul class="personal-info-ul">
|
||||
<li v-for="(v, k) in state.msgs as any" :key="k" class="personal-info-li">
|
||||
<a class="personal-info-li-title">{{ `[${$t(EnumValue.getLabelByValue(MsgTypeEnum, v.type))}] ${v.msg}` }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="!mt-4 resource-info">
|
||||
@@ -236,39 +219,11 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog width="900px" :title="$t('common.msg')" v-model="msgDialog.visible">
|
||||
<el-table border :data="msgDialog.msgs.list" size="small">
|
||||
<el-table-column property="type" :label="$t('common.type')" width="60">
|
||||
<template #default="scope">
|
||||
{{ $t(EnumValue.getLabelByValue(MsgTypeEnum, scope.row.type)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="msg" :label="$t('common.msg')"></el-table-column>
|
||||
<el-table-column property="createTime" :label="$t('common.time')" width="150">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row type="flex" class="mt-1" justify="center">
|
||||
<el-pagination
|
||||
small
|
||||
@current-change="searchMsg"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="msgDialog.msgs.total"
|
||||
v-model:current-page="msgDialog.query.pageNum"
|
||||
:page-size="msgDialog.query.pageSize"
|
||||
/>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, toRefs } from 'vue';
|
||||
import { computed, onMounted, reactive } from 'vue';
|
||||
// import * as echarts from 'echarts';
|
||||
import { formatAxis, formatDate } from '@/common/utils/format';
|
||||
import { indexApi } from './api';
|
||||
@@ -285,8 +240,6 @@ import { getAllTagInfoByCodePaths } from '../ops/component/tag';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getFileUrl, getUploadFileUrl } from '@/common/request';
|
||||
import { saveUser } from '@/common/utils/storage';
|
||||
import EnumValue from '../../common/Enum';
|
||||
import { MsgTypeEnum } from './enums';
|
||||
|
||||
const router = useRouter();
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
@@ -296,17 +249,6 @@ const state = reactive({
|
||||
roles: [],
|
||||
},
|
||||
msgs: [],
|
||||
msgDialog: {
|
||||
visible: false,
|
||||
query: {
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
},
|
||||
msgs: {
|
||||
list: [],
|
||||
total: null,
|
||||
},
|
||||
},
|
||||
resourceOpTableHeight: 180,
|
||||
defaultLogSize: 5,
|
||||
machine: {
|
||||
@@ -331,8 +273,6 @@ const state = reactive({
|
||||
},
|
||||
});
|
||||
|
||||
const { msgDialog } = toRefs(state);
|
||||
|
||||
const roleInfo = computed(() => {
|
||||
if (state.accountInfo.roles.length == 0) {
|
||||
return '';
|
||||
@@ -349,30 +289,12 @@ const currentTime = computed(() => {
|
||||
onMounted(() => {
|
||||
initData();
|
||||
getAccountInfo();
|
||||
|
||||
getMsgs().then((res) => {
|
||||
state.msgs = res.list;
|
||||
});
|
||||
});
|
||||
|
||||
const showMsgs = async () => {
|
||||
state.msgDialog.query.pageNum = 1;
|
||||
searchMsg();
|
||||
state.msgDialog.visible = true;
|
||||
};
|
||||
|
||||
const searchMsg = async () => {
|
||||
state.msgDialog.msgs = await getMsgs();
|
||||
};
|
||||
|
||||
const getAccountInfo = async () => {
|
||||
state.accountInfo = await personApi.accountInfo.request();
|
||||
};
|
||||
|
||||
const getMsgs = async () => {
|
||||
return await personApi.getMsgs.request(state.msgDialog.query);
|
||||
};
|
||||
|
||||
const beforeAvatarUpload = (rawFile: any) => {
|
||||
if (rawFile.size >= 512 * 1024) {
|
||||
ElMessage.error('头像不能超过512KB!');
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { EnumValue } from '@/common/Enum';
|
||||
|
||||
export const MsgTypeEnum = {
|
||||
Login: EnumValue.of(1, 'home.msgTypeLogin'),
|
||||
Notify: EnumValue.of(2, 'home.msgTypeNotify'),
|
||||
};
|
||||
@@ -151,7 +151,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { initRouter } from '@/router/index';
|
||||
import { getRefreshToken, saveRefreshToken, saveToken, saveUser } from '@/common/utils/storage';
|
||||
import openApi from '@/common/openApi';
|
||||
import { RsaEncrypt } from '@/common/rsa';
|
||||
import { RsaEncrypt } from '@/common/crypto';
|
||||
import { getAccountLoginSecurity, getLdapEnabled } from '@/common/sysconfig';
|
||||
import { letterAvatar } from '@/common/utils/string';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Api from '@/common/Api';
|
||||
import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
|
||||
export const machineApi = {
|
||||
@@ -67,9 +66,9 @@ export const cmdConfApi = {
|
||||
};
|
||||
|
||||
export function getMachineTerminalSocketUrl(authCertName: any) {
|
||||
return `${config.baseWsUrl}/machines/terminal/${authCertName}?${joinClientParams()}`;
|
||||
return `/machines/terminal/${authCertName}`;
|
||||
}
|
||||
|
||||
export function getMachineRdpSocketUrl(authCertName: any) {
|
||||
return `${config.baseWsUrl}/machines/rdp/${authCertName}`;
|
||||
return `/machines/rdp/${authCertName}`;
|
||||
}
|
||||
|
||||
@@ -5,5 +5,7 @@ export const personApi = {
|
||||
updateAccount: Api.newPut('/sys/accounts/self'),
|
||||
authStatus: Api.newGet('/auth/oauth2/status'),
|
||||
getMsgs: Api.newGet('/msgs/self'),
|
||||
getUnreadMsgCount: Api.newGet('/msgs/self/unread/count'),
|
||||
readMsg: Api.newGet('/msgs/self/read'),
|
||||
unbindOauth2: Api.newGet('/auth/oauth2/unbind'),
|
||||
};
|
||||
|
||||
@@ -1,45 +1,40 @@
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
import type { UserConfig } from 'vite';
|
||||
import { loadEnv } from './src/common/utils/viteBuild';
|
||||
import { CodeInspectorPlugin } from 'code-inspector-plugin';
|
||||
import progress from 'vite-plugin-progress';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { ConfigEnv, defineConfig, loadEnv } from 'vite';
|
||||
|
||||
const pathResolve = (dir: string): any => {
|
||||
export default defineConfig(({ mode }: ConfigEnv) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const pathResolve = (dir: string): any => {
|
||||
return resolve(__dirname, '.', dir);
|
||||
};
|
||||
};
|
||||
|
||||
const { VITE_PORT, VITE_OPEN, VITE_PUBLIC_PATH, VITE_EDITOR } = loadEnv();
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
const alias: Record<string, string> = {
|
||||
return {
|
||||
base: isProd ? env.VITE_PUBLIC_PATH : './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': pathResolve('src/'),
|
||||
};
|
||||
|
||||
const viteConfig: UserConfig = {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
CodeInspectorPlugin({
|
||||
bundler: 'vite',
|
||||
editor: VITE_EDITOR as any,
|
||||
editor: env.VITE_EDITOR as any,
|
||||
}),
|
||||
progress(),
|
||||
],
|
||||
root: process.cwd(),
|
||||
resolve: {
|
||||
alias,
|
||||
},
|
||||
base: isProd ? VITE_PUBLIC_PATH : './',
|
||||
optimizeDeps: {
|
||||
include: ['element-plus/es/locale/lang/zh-cn'],
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: VITE_PORT,
|
||||
open: VITE_OPEN,
|
||||
port: Number.parseInt(env.VITE_PORT) || 8889,
|
||||
open: env.VITE_OPEN === 'true',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:18888',
|
||||
@@ -53,15 +48,15 @@ const viteConfig: UserConfig = {
|
||||
chunkSizeWarningLimit: 1500,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `assets/[hash]-[name].js`,
|
||||
chunkFileNames: `assets/[hash]-[name].js`,
|
||||
assetFileNames: `assets/[hash]-[name].[ext]`,
|
||||
entryFileNames: `assets/js/[hash]-[name].js`,
|
||||
chunkFileNames: `assets/js/[hash]-[name].js`,
|
||||
assetFileNames: `assets/[ext]/[hash]-[name].[ext]`,
|
||||
hashCharacters: 'hex',
|
||||
advancedChunks: {
|
||||
groups: [
|
||||
{ name: 'vue-vendor', test: /[\\/]node_modules[\\/](vue|@vue|vue-router|pinia)[\\/]/ },
|
||||
{ name: 'echarts', test: /(echarts)/i },
|
||||
{ name: 'monaco', test: /(monaco-editor)/i },
|
||||
{ name: 'charts', test: /[\\/]node_modules[\\/](echarts)[\\/]/ },
|
||||
{ name: 'monaco', test: /[\\/]node_modules[\\/]monaco-editor[\\/]/ },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -88,6 +83,5 @@ const viteConfig: UserConfig = {
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default viteConfig;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -10,16 +10,16 @@ require (
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/go-ldap/ldap/v3 v3.4.11
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250508043914-ed57fa5c5274
|
||||
github.com/microsoft/go-mssqldb v1.9.1
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250630080345-f9402614f6ba
|
||||
github.com/microsoft/go-mssqldb v1.9.2
|
||||
github.com/mojocn/base64Captcha v1.3.8 // 验证码
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.9
|
||||
@@ -32,33 +32,32 @@ require (
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/veops/go-ansiterm v0.0.5
|
||||
go.mongodb.org/mongo-driver/v2 v2.2.2 // mongo
|
||||
golang.org/x/crypto v0.39.0 // ssh
|
||||
golang.org/x/crypto v0.40.0 // ssh
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sync v0.16.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
// gorm
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
gorm.io/gorm v1.30.1
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
@@ -66,15 +65,16 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
@@ -83,20 +83,20 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
golang.org/x/arch v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
golang.org/x/arch v0.19.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
modernc.org/libc v1.66.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.38.1 // indirect
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ func (a *AccountLogin) ReqConfs() *req.Confs {
|
||||
|
||||
// @router /auth/accounts/login [post]
|
||||
func (a *AccountLogin) Login(rc *req.Ctx) {
|
||||
loginForm := req.BindJsonAndValid[*form.LoginForm](rc)
|
||||
loginForm := req.BindJson[*form.LoginForm](rc)
|
||||
ctx := rc.MetaCtx
|
||||
|
||||
accountLoginSecurity := config.GetAccountLoginSecurity()
|
||||
@@ -96,7 +96,7 @@ type OtpVerifyInfo struct {
|
||||
|
||||
// OTP双因素校验
|
||||
func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
||||
otpVerify := req.BindJsonAndValid[*form.OtpVerfiy](rc)
|
||||
otpVerify := req.BindJson[*form.OtpVerfiy](rc)
|
||||
ctx := rc.MetaCtx
|
||||
|
||||
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"mayfly-go/internal/auth/config"
|
||||
"mayfly-go/internal/auth/imsg"
|
||||
"mayfly-go/internal/auth/pkg/otp"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgentity "mayfly-go/internal/msg/domain/entity"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/netx"
|
||||
@@ -114,14 +114,12 @@ func saveLogin(ctx context.Context, account *sysentity.Account, ip string) {
|
||||
// 偷懒为了方便直接获取accountApp
|
||||
biz.ErrIsNil(sysapp.GetAccountApp().Update(context.TODO(), updateAccount))
|
||||
|
||||
// 创建登录消息
|
||||
loginMsg := &msgentity.Msg{
|
||||
RecipientId: int64(account.Id),
|
||||
Msg: i18n.TC(ctx, imsg.LoginMsg, "ip", ip, "time", timex.DefaultFormat(now)),
|
||||
Type: 1,
|
||||
}
|
||||
loginMsg.CreateTime = &now
|
||||
loginMsg.Creator = account.Username
|
||||
loginMsg.CreatorId = account.Id
|
||||
msgapp.GetMsgApp().Create(context.TODO(), loginMsg)
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, &msgdto.MsgTmplSendEvent{
|
||||
TmplChannel: msgdto.MsgTmplLogin,
|
||||
Params: collx.M{
|
||||
"ip": ip,
|
||||
"time": timex.DefaultFormat(now),
|
||||
},
|
||||
ReceiverIds: []uint64{account.Id},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (a *LdapLogin) GetLdapEnabled(rc *req.Ctx) {
|
||||
|
||||
// @router /auth/ldap/login [post]
|
||||
func (a *LdapLogin) Login(rc *req.Ctx) {
|
||||
loginForm := req.BindJsonAndValid[*form.LoginForm](rc)
|
||||
loginForm := req.BindJson[*form.LoginForm](rc)
|
||||
ctx := rc.MetaCtx
|
||||
accountLoginSecurity := config.GetAccountLoginSecurity()
|
||||
// 判断是否有开启登录验证码校验
|
||||
@@ -197,14 +197,14 @@ func dial(ldapConf *config.LdapLogin) (*ldap.Conn, error) {
|
||||
InsecureSkipVerify: ldapConf.SkipTLSVerify,
|
||||
}
|
||||
if ldapConf.SecurityProtocol == "LDAPS" {
|
||||
conn, err := ldap.DialTLS("tcp", addr, tlsConfig)
|
||||
conn, err := ldap.DialURL("ldaps://"+addr, ldap.DialWithTLSConfig(tlsConfig))
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("dial TLS: %v", err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
conn, err := ldap.Dial("tcp", addr)
|
||||
conn, err := ldap.DialURL("ldap://" + addr)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("dial: %v", err)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ var En = map[i18n.MsgId]string{
|
||||
ErrOtpCheckRestrict: "Two-factor validation failed more than 5 times. Try again in 10 minutes",
|
||||
ErrOtpCheckFail: "Two-factor authentication authorization code is incorrect",
|
||||
ErrAccountNotAvailable: "Account is not available",
|
||||
LoginMsg: "Log in to [{{.ip}}]-[{{.time}}]",
|
||||
ErrUsernameOrPwdErr: "Wrong username or password",
|
||||
ErrOauth2NoAutoRegister: "the system does not enable automatic registration, please ask the administrator to add the corresponding account first",
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ const (
|
||||
ErrOtpCheckRestrict
|
||||
ErrOtpCheckFail
|
||||
ErrAccountNotAvailable
|
||||
LoginMsg
|
||||
ErrUsernameOrPwdErr
|
||||
ErrOauth2NoAutoRegister
|
||||
)
|
||||
|
||||
@@ -15,7 +15,6 @@ var Zh_CN = map[i18n.MsgId]string{
|
||||
ErrOtpCheckRestrict: "双因素校验失败超过5次, 请10分钟后再试",
|
||||
ErrOtpCheckFail: "双因素认证授权码不正确",
|
||||
ErrAccountNotAvailable: "账号不可用",
|
||||
LoginMsg: "于[{{.ip}}]-[{{.time}}]登录",
|
||||
ErrUsernameOrPwdErr: "用户名或密码错误",
|
||||
ErrOauth2NoAutoRegister: "系统未开启自动注册, 请先让管理员添加对应账号",
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/auth/api"
|
||||
"mayfly-go/internal/auth/application"
|
||||
"mayfly-go/internal/auth/infrastructure/persistence"
|
||||
"mayfly-go/internal/auth/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -11,15 +11,13 @@ import (
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/imsg"
|
||||
"mayfly-go/internal/event"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
"mayfly-go/internal/pkg/utils"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -36,7 +34,6 @@ type Db struct {
|
||||
instanceApp application.Instance `inject:"T"`
|
||||
dbApp application.Db `inject:"T"`
|
||||
dbSqlExecApp application.DbSqlExec `inject:"T"`
|
||||
msgApp msgapp.Msg `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
|
||||
@@ -135,7 +132,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
|
||||
/** 数据库操作相关、执行sql等 ***/
|
||||
|
||||
func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
form := req.BindJsonAndValid[*form.DbSqlExecForm](rc)
|
||||
form := req.BindJson[*form.DbSqlExecForm](rc)
|
||||
|
||||
ctx, cancel := context.WithTimeout(rc.MetaCtx, time.Duration(config.GetDbms().SqlExecTl)*time.Second)
|
||||
defer cancel()
|
||||
@@ -167,17 +164,6 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
rc.ResData = execRes
|
||||
}
|
||||
|
||||
// progressCategory sql文件执行进度消息类型
|
||||
const progressCategory = "execSqlFileProgress"
|
||||
|
||||
// progressMsg sql文件执行进度消息
|
||||
type progressMsg struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ExecutedStatements int `json:"executedStatements"`
|
||||
Terminated bool `json:"terminated"`
|
||||
}
|
||||
|
||||
// 执行sql文件
|
||||
func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
multipart, err := rc.GetRequest().MultipartReader()
|
||||
@@ -246,7 +232,11 @@ func (d *Db) DumpSql(rc *req.Ctx) {
|
||||
if len(msg) > 0 {
|
||||
msg = "DB dump error: " + msg
|
||||
rc.GetWriter().Write([]byte(msg))
|
||||
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.DbDumpErr), msg))
|
||||
global.EventBus.Publish(rc.MetaCtx, event.EventTopicMsgTmplSend, &msgdto.MsgTmplSendEvent{
|
||||
TmplChannel: msgdto.MsgTmplDbDumpFail,
|
||||
Params: collx.M{"error": msg},
|
||||
ReceiverIds: []uint64{la.Id},
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ func (d *DataSyncTask) DeleteTask(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
|
||||
form := req.BindJsonAndValid[*form.DataSyncTaskStatusForm](rc)
|
||||
form := req.BindJson[*form.DataSyncTaskStatusForm](rc)
|
||||
rc.ReqParam = form
|
||||
|
||||
task, err := d.dataSyncTaskApp.GetById(form.Id)
|
||||
|
||||
@@ -30,7 +30,7 @@ func (d *DbSql) ReqConfs() *req.Confs {
|
||||
|
||||
// @router /api/db/:dbId/sql [post]
|
||||
func (d *DbSql) SaveSql(rc *req.Ctx) {
|
||||
dbSqlForm := req.BindJsonAndValid[*form.DbSqlSaveForm](rc)
|
||||
dbSqlForm := req.BindJson[*form.DbSqlSaveForm](rc)
|
||||
rc.ReqParam = dbSqlForm
|
||||
|
||||
dbId := getDbId(rc)
|
||||
|
||||
@@ -93,7 +93,7 @@ func (d *DbTransferTask) DeleteTask(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) ChangeStatus(rc *req.Ctx) {
|
||||
form := req.BindJsonAndValid[*form.DbTransferTaskStatusForm](rc)
|
||||
form := req.BindJson[*form.DbTransferTaskStatusForm](rc)
|
||||
rc.ReqParam = form
|
||||
|
||||
task, err := d.dbTransferTaskApp.GetById(form.Id)
|
||||
@@ -136,7 +136,7 @@ func (d *DbTransferTask) FileDel(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
fm := req.BindJsonAndValid[*form.DbTransferFileRunForm](rc)
|
||||
fm := req.BindJson[*form.DbTransferFileRunForm](rc)
|
||||
|
||||
rc.ReqParam = fm
|
||||
|
||||
|
||||
@@ -13,19 +13,19 @@ import (
|
||||
"mayfly-go/internal/db/imsg"
|
||||
flowapp "mayfly-go/internal/flow/application"
|
||||
flowentity "mayfly-go/internal/flow/domain/entity"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type sqlExecParam struct {
|
||||
@@ -71,7 +71,6 @@ type dbSqlExecAppImpl struct {
|
||||
dbSqlExecRepo repository.DbSqlExec `inject:"T"`
|
||||
|
||||
flowProcdefApp flowapp.Procdef `inject:"T"`
|
||||
msgApp msgapp.Msg `inject:"T"`
|
||||
}
|
||||
|
||||
func createSqlExecRecord(ctx context.Context, execSqlReq *dto.DbSqlExecReq, sql string) *entity.DbSqlExec {
|
||||
@@ -206,38 +205,55 @@ func (d *dbSqlExecAppImpl) ExecReader(ctx context.Context, execReader *dto.SqlRe
|
||||
la := contextx.GetLoginAccount(ctx)
|
||||
needSendMsg := la != nil && clientId != ""
|
||||
|
||||
startTime := time.Now()
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
|
||||
msgEvent := &msgdto.MsgTmplSendEvent{
|
||||
TmplChannel: msgdto.MsgTmplSqlScriptRunSuccess,
|
||||
Params: collx.M{"filename": filename, "db": dbConn.Info.GetLogDesc()},
|
||||
}
|
||||
|
||||
progressMsgEvent := &msgdto.MsgTmplSendEvent{
|
||||
TmplChannel: msgdto.MsgTmplSqlScriptRunProgress,
|
||||
Params: collx.M{
|
||||
"id": progressId,
|
||||
"title": filename,
|
||||
"executedStatements": executedStatements,
|
||||
"terminated": false,
|
||||
"clientId": clientId,
|
||||
},
|
||||
}
|
||||
|
||||
if needSendMsg {
|
||||
msgEvent.ReceiverIds = []uint64{la.Id}
|
||||
progressMsgEvent.ReceiverIds = []uint64{la.Id}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if needSendMsg {
|
||||
progressMsgEvent.Params["terminated"] = true
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, progressMsgEvent)
|
||||
}
|
||||
|
||||
if err := recover(); err != nil {
|
||||
errInfo := anyx.ToString(err)
|
||||
logx.Errorf("exec sql reader error: %s", errInfo)
|
||||
if needSendMsg {
|
||||
errInfo = stringx.Truncate(errInfo, 300, 10, "...")
|
||||
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] execution failure: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)).WithClientId(clientId))
|
||||
msgEvent.TmplChannel = msgdto.MsgTmplSqlScriptRunFail
|
||||
msgEvent.Params["error"] = errInfo
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
if needSendMsg {
|
||||
defer ws.SendJsonMsg(ws.UserId(la.Id), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: true,
|
||||
}).WithCategory(progressCategory))
|
||||
}
|
||||
|
||||
tx, _ := dbConn.Begin()
|
||||
err := sqlparser.SQLSplit(execReader.Reader, ';', func(sql string) error {
|
||||
if executedStatements%50 == 0 {
|
||||
if needSendMsg {
|
||||
ws.SendJsonMsg(ws.UserId(la.Id), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: false,
|
||||
}).WithCategory(progressCategory))
|
||||
progressMsgEvent.Params["executedStatements"] = executedStatements
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, progressMsgEvent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,12 +265,18 @@ func (d *dbSqlExecAppImpl) ExecReader(ctx context.Context, execReader *dto.SqlRe
|
||||
})
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
if needSendMsg {
|
||||
msgEvent.TmplChannel = msgdto.MsgTmplSqlScriptRunFail
|
||||
msgEvent.Params["error"] = err.Error()
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
|
||||
}
|
||||
return err
|
||||
}
|
||||
_ = tx.Commit()
|
||||
|
||||
if needSendMsg {
|
||||
d.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), "execution success").WithClientId(clientId))
|
||||
msgEvent.Params["cost"] = fmt.Sprintf("%dms", time.Since(startTime).Milliseconds())
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,10 +14,7 @@ var En = map[i18n.MsgId]string{
|
||||
LogDbRunSql: "DB - Run SQL",
|
||||
LogDbDump: "DB - Export DB",
|
||||
|
||||
SqlScriptRunFail: "sql script failed to execute",
|
||||
SqlScriptRunSuccess: "sql script executed successfully",
|
||||
SqlScripRunProgress: "sql execution progress",
|
||||
DbDumpErr: "Database export failed",
|
||||
ErrDbNameExist: "The database name already exists in this instance",
|
||||
ErrDbNotAccess: "The operation permissions of database [{{.dbName}}] are not configured",
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ const (
|
||||
LogDbRunSqlFile
|
||||
LogDbDump
|
||||
|
||||
SqlScriptRunFail
|
||||
SqlScriptRunSuccess
|
||||
SqlScripRunProgress
|
||||
DbDumpErr
|
||||
ErrDbNameExist
|
||||
ErrDbNotAccess
|
||||
|
||||
|
||||
@@ -14,10 +14,7 @@ var Zh_CN = map[i18n.MsgId]string{
|
||||
LogDbRunSql: "DB-运行SQL",
|
||||
LogDbDump: "DB-导出数据库",
|
||||
|
||||
SqlScriptRunFail: "sql脚本执行失败",
|
||||
SqlScriptRunSuccess: "sql脚本执行成功",
|
||||
SqlScripRunProgress: "sql执行进度",
|
||||
DbDumpErr: "数据库导出失败",
|
||||
ErrDbNameExist: "该实例下数据库名已存在",
|
||||
ErrDbNotAccess: "未配置数据库【{{.dbName}}】的操作权限",
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/db/api"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/infrastructure/persistence"
|
||||
"mayfly-go/internal/db/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/es/api"
|
||||
"mayfly-go/internal/es/application"
|
||||
"mayfly-go/internal/es/infrastructure/persistence"
|
||||
"mayfly-go/internal/es/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/internal/file/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type fileRepoImpl struct {
|
||||
base.RepoImpl[*entity.File]
|
||||
}
|
||||
|
||||
func newFileRepo() repository.File {
|
||||
return &fileRepoImpl{}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(newFileRepo(), ioc.WithComponentName("FileRepo"))
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/file/api"
|
||||
"mayfly-go/internal/file/application"
|
||||
"mayfly-go/internal/file/infrastructure/persistence"
|
||||
"mayfly-go/internal/file/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -97,7 +97,7 @@ func (a *Procdef) Save(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (a *Procdef) SaveFlowDef(rc *req.Ctx) {
|
||||
form := req.BindJsonAndValid[*form.ProcdefFlow](rc)
|
||||
form := req.BindJson[*form.ProcdefFlow](rc)
|
||||
rc.ReqParam = form
|
||||
|
||||
biz.ErrIsNil(a.procdefApp.SaveFlowDef(rc.MetaCtx, &dto.SaveFlowDef{
|
||||
|
||||
@@ -47,7 +47,7 @@ func (p *Procinst) GetProcinstPage(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (p *Procinst) ProcinstStart(rc *req.Ctx) {
|
||||
startForm := req.BindJsonAndValid[*form.ProcinstStart](rc)
|
||||
startForm := req.BindJson[*form.ProcinstStart](rc)
|
||||
_, err := p.procinstApp.StartProc(rc.MetaCtx, startForm.ProcdefId, &dto.StarProc{
|
||||
BizType: startForm.BizType,
|
||||
BizForm: jsonx.ToStr(startForm.BizForm),
|
||||
|
||||
@@ -74,7 +74,7 @@ func (p *ProcinstTask) GetTasks(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (p *ProcinstTask) PassTask(rc *req.Ctx) {
|
||||
auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc)
|
||||
auditForm := req.BindJson[*form.ProcinstTaskAudit](rc)
|
||||
rc.ReqParam = auditForm
|
||||
|
||||
la := rc.GetLoginAccount()
|
||||
@@ -84,7 +84,7 @@ func (p *ProcinstTask) PassTask(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (p *ProcinstTask) RejectTask(rc *req.Ctx) {
|
||||
auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc)
|
||||
auditForm := req.BindJson[*form.ProcinstTaskAudit](rc)
|
||||
rc.ReqParam = auditForm
|
||||
|
||||
la := rc.GetLoginAccount()
|
||||
@@ -94,7 +94,7 @@ func (p *ProcinstTask) RejectTask(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (p *ProcinstTask) BackTask(rc *req.Ctx) {
|
||||
auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc)
|
||||
auditForm := req.BindJson[*form.ProcinstTaskAudit](rc)
|
||||
rc.ReqParam = auditForm
|
||||
biz.ErrIsNil(p.procinstTaskApp.BackTask(rc.MetaCtx, dto.UserTaskOp{TaskId: auditForm.Id, Remark: auditForm.Remark}))
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/internal/flow/imsg"
|
||||
"mayfly-go/internal/flow/infrastructure/persistence"
|
||||
"mayfly-go/internal/flow/infra/persistence"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/global"
|
||||
"strings"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/flow/api"
|
||||
"mayfly-go/internal/flow/application"
|
||||
"mayfly-go/internal/flow/infrastructure/persistence"
|
||||
"mayfly-go/internal/flow/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/machine/api/form"
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/application"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"mayfly-go/internal/machine/imsg"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
"mayfly-go/internal/pkg/consts"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
|
||||
@@ -12,15 +12,14 @@ import (
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/imsg"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"mime/multipart"
|
||||
@@ -36,7 +35,6 @@ import (
|
||||
|
||||
type MachineFile struct {
|
||||
machineFileApp application.MachineFile `inject:"T"`
|
||||
msgApp msgapp.Msg `inject:"T"`
|
||||
}
|
||||
|
||||
func (mf *MachineFile) ReqConfs() *req.Confs {
|
||||
@@ -106,7 +104,7 @@ func (m *MachineFile) DeleteFile(rc *req.Ctx) {
|
||||
/*** sftp相关操作 */
|
||||
|
||||
func (m *MachineFile) CreateFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid[*form.CreateFileForm](rc)
|
||||
opForm := req.BindJson[*form.CreateFileForm](rc)
|
||||
path := opForm.Path
|
||||
|
||||
attrs := collx.Kvs("path", path)
|
||||
@@ -239,7 +237,7 @@ func (m *MachineFile) GetFileStat(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid[*form.WriteFileContentForm](rc)
|
||||
opForm := req.BindJson[*form.WriteFileContentForm](rc)
|
||||
path := opForm.Path
|
||||
|
||||
mi, err := m.machineFileApp.WriteFileContent(rc.MetaCtx, opForm.MachineFileOp, []byte(opForm.Content))
|
||||
@@ -264,14 +262,6 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
||||
file, _ := fileheader.Open()
|
||||
defer file.Close()
|
||||
|
||||
la := rc.GetLoginAccount()
|
||||
defer func() {
|
||||
if anyx.ToString(recover()) != "" {
|
||||
logx.Errorf("upload file error: %s", err)
|
||||
m.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.TC(ctx, imsg.ErrFileUploadFail), fmt.Sprintf("%s: \n<-e : %s", i18n.TC(ctx, imsg.ErrFileUploadFail), err)))
|
||||
}
|
||||
}()
|
||||
|
||||
opForm := &dto.MachineFileOp{
|
||||
MachineId: machineId,
|
||||
AuthCertName: authCertName,
|
||||
@@ -281,9 +271,27 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
||||
|
||||
mi, err := m.machineFileApp.UploadFile(ctx, opForm, fileheader.Filename, file)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
|
||||
|
||||
// 发送文件上传结果消息
|
||||
msgEvent := &msgdto.MsgTmplSendEvent{
|
||||
TmplChannel: msgdto.MsgTmplMachineFileUploadSuccess,
|
||||
Params: collx.M{
|
||||
"filename": fileheader.Filename,
|
||||
"path": path,
|
||||
},
|
||||
ReceiverIds: []uint64{rc.GetLoginAccount().Id},
|
||||
}
|
||||
if err != nil {
|
||||
msgEvent.Params["error"] = err.Error()
|
||||
msgEvent.TmplChannel = msgdto.MsgTmplMachineFileUploadFail
|
||||
}
|
||||
if mi != nil {
|
||||
msgEvent.Params["machineName"] = mi.Name
|
||||
msgEvent.Params["machineIp"] = mi.Ip
|
||||
}
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
|
||||
|
||||
biz.ErrIsNilAppendErr(err, "upload file error: %s")
|
||||
// 保存消息并发送文件上传成功通知
|
||||
m.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", fileheader.Filename, mi.Name, mi.Ip, path)))
|
||||
}
|
||||
|
||||
type FolderFile struct {
|
||||
@@ -350,6 +358,17 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
msgEvent := &msgdto.MsgTmplSendEvent{
|
||||
TmplChannel: msgdto.MsgTmplMachineFileUploadSuccess,
|
||||
Params: collx.M{
|
||||
"filename": folderName,
|
||||
"path": basePath,
|
||||
"machineName": mi.Name,
|
||||
"machineIp": mi.Ip,
|
||||
},
|
||||
ReceiverIds: []uint64{rc.GetLoginAccount().Id},
|
||||
}
|
||||
|
||||
// 分组处理
|
||||
groupNum := 30
|
||||
chunks := collx.ArraySplit(folderFiles, groupNum)
|
||||
@@ -359,7 +378,6 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
wg.Add(len(chunks))
|
||||
|
||||
isSuccess := true
|
||||
la := rc.GetLoginAccount()
|
||||
for _, chunk := range chunks {
|
||||
go func(files []FolderFile, wg *sync.WaitGroup) {
|
||||
defer func() {
|
||||
@@ -370,7 +388,11 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
logx.Errorf("upload file error: %s", err)
|
||||
switch t := err.(type) {
|
||||
case *errorx.BizError:
|
||||
m.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.TC(ctx, imsg.ErrFileUploadFail), fmt.Sprintf("%s: \n<-e errCode: %d, errMsg: %s", i18n.TC(ctx, imsg.ErrFileUploadFail), t.Code(), t.Error())))
|
||||
{
|
||||
msgEvent.TmplChannel = msgdto.MsgTmplMachineFileUploadFail
|
||||
msgEvent.Params["error"] = t.Error()
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -394,13 +416,12 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
// 等待所有协程执行完成
|
||||
wg.Wait()
|
||||
if isSuccess {
|
||||
// 保存消息并发送文件上传成功通知
|
||||
m.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", folderName, mi.Name, mi.Ip, basePath)))
|
||||
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid[*form.RemoveFileForm](rc)
|
||||
opForm := req.BindJson[*form.RemoveFileForm](rc)
|
||||
|
||||
mi, err := m.machineFileApp.RemoveFile(rc.MetaCtx, opForm.MachineFileOp, opForm.Paths...)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
|
||||
@@ -408,21 +429,21 @@ func (m *MachineFile) RemoveFile(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) CopyFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid[*form.CopyFileForm](rc)
|
||||
opForm := req.BindJson[*form.CopyFileForm](rc)
|
||||
mi, err := m.machineFileApp.Copy(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
|
||||
biz.ErrIsNilAppendErr(err, "file copy error: %s")
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
|
||||
}
|
||||
|
||||
func (m *MachineFile) MvFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid[*form.CopyFileForm](rc)
|
||||
opForm := req.BindJson[*form.CopyFileForm](rc)
|
||||
mi, err := m.machineFileApp.Mv(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
|
||||
biz.ErrIsNilAppendErr(err, "file move error: %s")
|
||||
}
|
||||
|
||||
func (m *MachineFile) Rename(rc *req.Ctx) {
|
||||
renameForm := req.BindJsonAndValid[*form.RenameForm](rc)
|
||||
renameForm := req.BindJson[*form.RenameForm](rc)
|
||||
mi, err := m.machineFileApp.Rename(rc.MetaCtx, renameForm.MachineFileOp, renameForm.Newname)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
|
||||
biz.ErrIsNilAppendErr(err, "file rename error: %s")
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
"mayfly-go/internal/machine/imsg"
|
||||
"mayfly-go/internal/machine/infrastructure/cache"
|
||||
"mayfly-go/internal/machine/infra/cache"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagdto "mayfly-go/internal/tag/application/dto"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/machine/api"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/infrastructure/persistence"
|
||||
"mayfly-go/internal/machine/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -2,13 +2,13 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/mongo/api/form"
|
||||
"mayfly-go/internal/mongo/api/vo"
|
||||
"mayfly-go/internal/mongo/application"
|
||||
"mayfly-go/internal/mongo/domain/entity"
|
||||
"mayfly-go/internal/mongo/imsg"
|
||||
"mayfly-go/internal/pkg/consts"
|
||||
"mayfly-go/internal/pkg/event"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
@@ -146,7 +146,7 @@ func (m *Mongo) Collections(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *Mongo) RunCommand(rc *req.Ctx) {
|
||||
commandForm := req.BindJsonAndValid[*form.MongoRunCommand](rc)
|
||||
commandForm := req.BindJson[*form.MongoRunCommand](rc)
|
||||
|
||||
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
|
||||
biz.ErrIsNil(err)
|
||||
@@ -176,7 +176,7 @@ func (m *Mongo) RunCommand(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *Mongo) FindCommand(rc *req.Ctx) {
|
||||
commandForm := req.BindJsonAndValid[*form.MongoFindCommand](rc)
|
||||
commandForm := req.BindJson[*form.MongoFindCommand](rc)
|
||||
|
||||
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
|
||||
biz.ErrIsNil(err)
|
||||
@@ -211,7 +211,7 @@ func (m *Mongo) FindCommand(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) {
|
||||
commandForm := req.BindJsonAndValid[*form.MongoUpdateByIdCommand](rc)
|
||||
commandForm := req.BindJson[*form.MongoUpdateByIdCommand](rc)
|
||||
|
||||
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
|
||||
biz.ErrIsNil(err)
|
||||
@@ -235,7 +235,7 @@ func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) {
|
||||
commandForm := req.BindJsonAndValid[*form.MongoUpdateByIdCommand](rc)
|
||||
commandForm := req.BindJson[*form.MongoUpdateByIdCommand](rc)
|
||||
|
||||
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
|
||||
biz.ErrIsNil(err)
|
||||
@@ -258,7 +258,7 @@ func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *Mongo) InsertOneCommand(rc *req.Ctx) {
|
||||
commandForm := req.BindJsonAndValid[*form.MongoInsertCommand](rc)
|
||||
commandForm := req.BindJson[*form.MongoInsertCommand](rc)
|
||||
|
||||
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/mongo/api"
|
||||
"mayfly-go/internal/mongo/application"
|
||||
"mayfly-go/internal/mongo/infrastructure/persistence"
|
||||
"mayfly-go/internal/mongo/infra/persistence"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -14,12 +14,14 @@ type Msg struct {
|
||||
func (m *Msg) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("/self", m.GetMsgs),
|
||||
req.NewGet("/self/unread/count", m.GetUnreadCount),
|
||||
req.NewGet("/self/read", m.ReadMsg),
|
||||
}
|
||||
|
||||
return req.NewConfs("/msgs", reqs[:]...)
|
||||
}
|
||||
|
||||
// 获取账号接收的消息列表
|
||||
// GetMsgs 获取账号接收的消息列表
|
||||
func (m *Msg) GetMsgs(rc *req.Ctx) {
|
||||
condition := &entity.Msg{
|
||||
RecipientId: int64(rc.GetLoginAccount().Id),
|
||||
@@ -28,3 +30,25 @@ func (m *Msg) GetMsgs(rc *req.Ctx) {
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
// GetUnreadCount 获取账号接收的未读消息数量
|
||||
func (m *Msg) GetUnreadCount(rc *req.Ctx) {
|
||||
condition := &entity.Msg{
|
||||
RecipientId: int64(rc.GetLoginAccount().Id),
|
||||
Status: entity.MsgStatusUnRead,
|
||||
}
|
||||
rc.ResData = m.msgApp.CountByCond(condition)
|
||||
}
|
||||
|
||||
// ReadMsg 将账号接收的未读消息标记为已读
|
||||
func (m *Msg) ReadMsg(rc *req.Ctx) {
|
||||
cond := &entity.Msg{
|
||||
RecipientId: int64(rc.GetLoginAccount().Id),
|
||||
Status: entity.MsgStatusUnRead,
|
||||
}
|
||||
cond.Id = uint64(rc.QueryInt("id"))
|
||||
|
||||
biz.ErrIsNil(m.msgApp.UpdateByCond(rc.MetaCtx, &entity.Msg{
|
||||
Status: entity.MsgStatusRead,
|
||||
}, cond))
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (m *MsgTmpl) DelMsgTmpls(rc *req.Ctx) {
|
||||
|
||||
func (m *MsgTmpl) SendMsg(rc *req.Ctx) {
|
||||
code := rc.PathParam("code")
|
||||
form := req.BindJsonAndValid[*form.SendMsg](rc)
|
||||
form := req.BindJson[*form.SendMsg](rc)
|
||||
|
||||
rc.ReqParam = form
|
||||
|
||||
|
||||
@@ -15,3 +15,7 @@ func InitIoc() {
|
||||
func GetMsgApp() Msg {
|
||||
return ioc.Get[Msg]("MsgApp")
|
||||
}
|
||||
|
||||
func GetMsgTmplApp() MsgTmpl {
|
||||
return ioc.Get[MsgTmpl]("MsgTmplApp")
|
||||
}
|
||||
|
||||
@@ -2,35 +2,93 @@ package dto
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/msg/domain/entity"
|
||||
"mayfly-go/internal/msg/imsg"
|
||||
"mayfly-go/internal/msg/msgx"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
|
||||
type MsgTmplSave struct {
|
||||
model.ExtraData
|
||||
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Remark string `json:"remark"`
|
||||
Status entity.MsgTmplStatus `json:"status" `
|
||||
Title string `json:"title"`
|
||||
Tmpl string `json:"type"`
|
||||
MsgType msgx.MsgType `json:"msgType"`
|
||||
|
||||
ChannelIds []uint64 `json:"channelIds"`
|
||||
}
|
||||
|
||||
// MsgTmplBizSave 消息模板关联业务信息
|
||||
type MsgTmplBizSave struct {
|
||||
TmplId uint64 // 消息模板id
|
||||
BizId uint64 // 业务id
|
||||
BizType string
|
||||
}
|
||||
|
||||
// BizMsgTmplSend 业务消息模板发送消息
|
||||
type BizMsgTmplSend struct {
|
||||
BizId uint64 // 业务id
|
||||
BizType string
|
||||
type MsgTmplSendEvent struct {
|
||||
TmplChannel *MsgTmplChannel
|
||||
Params map[string]any // 模板占位符参数
|
||||
ReceiverIds []uint64 // 接收人id
|
||||
}
|
||||
|
||||
type MsgTmplChannel struct {
|
||||
Tmpl *entity.MsgTmpl
|
||||
Channels []*entity.MsgChannel
|
||||
}
|
||||
|
||||
var (
|
||||
MsgChannelSite = &entity.MsgChannel{
|
||||
Type: msgx.ChannelTypeSiteMsg,
|
||||
Status: entity.ChannelStatusEnable,
|
||||
}
|
||||
|
||||
MsgChannelWs = &entity.MsgChannel{
|
||||
Type: msgx.ChannelTypeWs,
|
||||
Status: entity.ChannelStatusEnable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
MsgTmplLogin = newMsgTmpl(entity.MsgTypeNotify,
|
||||
entity.MsgSubtypeUserLogin,
|
||||
entity.MsgStatusRead,
|
||||
imsg.LoginMsg,
|
||||
MsgChannelSite)
|
||||
|
||||
MsgTmplMachineFileUploadSuccess = newMsgTmpl(entity.MsgTypeNotify,
|
||||
entity.MsgSubtypeMachineFileUploadSuccess,
|
||||
entity.MsgStatusRead,
|
||||
imsg.MachineFileUploadSuccessMsg,
|
||||
MsgChannelSite, MsgChannelWs)
|
||||
|
||||
MsgTmplMachineFileUploadFail = newMsgTmpl(entity.MsgTypeNotify,
|
||||
entity.MsgSubtypeMachineFileUploadFail,
|
||||
entity.MsgStatusRead,
|
||||
imsg.MachineFileUploadFailMsg,
|
||||
MsgChannelSite, MsgChannelWs)
|
||||
|
||||
MsgTmplDbDumpFail = newMsgTmpl(entity.MsgTypeNotify,
|
||||
entity.MsgSubtypeDbDumpFail,
|
||||
entity.MsgStatusRead,
|
||||
imsg.DbDumpFailMsg,
|
||||
MsgChannelSite, MsgChannelWs)
|
||||
|
||||
MsgTmplSqlScriptRunFail = newMsgTmpl(entity.MsgTypeNotify,
|
||||
entity.MsgSubtypeSqlScriptRunFail,
|
||||
entity.MsgStatusRead,
|
||||
imsg.SqlScriptRunFailMsg,
|
||||
MsgChannelSite, MsgChannelWs)
|
||||
|
||||
MsgTmplSqlScriptRunSuccess = newMsgTmpl(entity.MsgTypeNotify,
|
||||
entity.MsgSubtypeSqlScriptRunSuccess,
|
||||
entity.MsgStatusRead,
|
||||
imsg.SqlScriptRunSuccessMsg,
|
||||
MsgChannelSite, MsgChannelWs)
|
||||
|
||||
MsgTmplSqlScriptRunProgress = &MsgTmplChannel{
|
||||
Tmpl: &entity.MsgTmpl{
|
||||
ExtraData: model.ExtraData{
|
||||
Extra: collx.M{
|
||||
"category": "sqlScriptRunProgress",
|
||||
},
|
||||
},
|
||||
},
|
||||
Channels: []*entity.MsgChannel{MsgChannelWs},
|
||||
}
|
||||
)
|
||||
|
||||
func newMsgTmpl(mtype entity.MsgType, subtype entity.MsgSubtype, status entity.MsgStatus, msgId i18n.MsgId, channels ...*entity.MsgChannel) *MsgTmplChannel {
|
||||
msgTmpl := &entity.MsgTmpl{}
|
||||
msgTmpl.SetExtraValue("msgId", msgId)
|
||||
msgTmpl.SetExtraValue("subtype", subtype)
|
||||
msgTmpl.SetExtraValue("type", mtype)
|
||||
msgTmpl.SetExtraValue("status", status)
|
||||
return &MsgTmplChannel{
|
||||
Tmpl: msgTmpl,
|
||||
Channels: channels,
|
||||
}
|
||||
}
|
||||
|
||||
43
server/internal/msg/application/dto/msg_tmpl.go
Normal file
43
server/internal/msg/application/dto/msg_tmpl.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/msg/domain/entity"
|
||||
"mayfly-go/internal/msg/msgx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type MsgTmplSave struct {
|
||||
model.ExtraData
|
||||
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Remark string `json:"remark"`
|
||||
Status entity.MsgTmplStatus `json:"status" `
|
||||
Title string `json:"title"`
|
||||
Tmpl string `json:"type"`
|
||||
MsgType msgx.MsgType `json:"msgType"`
|
||||
|
||||
ChannelIds []uint64 `json:"channelIds"`
|
||||
}
|
||||
|
||||
// MsgTmplBizSave 消息模板关联业务信息
|
||||
type MsgTmplBizSave struct {
|
||||
TmplId uint64 // 消息模板id
|
||||
BizId uint64 // 业务id
|
||||
BizType string
|
||||
}
|
||||
|
||||
// BizMsgTmplSend 业务消息模板发送消息
|
||||
type BizMsgTmplSend struct {
|
||||
BizId uint64 // 业务id
|
||||
BizType string
|
||||
Params map[string]any // 模板占位符参数
|
||||
ReceiverIds []uint64 // 接收人id
|
||||
}
|
||||
|
||||
type MsgTmplSend struct {
|
||||
Tmpl *entity.MsgTmpl
|
||||
Channels []*entity.MsgChannel
|
||||
Params map[string]any // 模板占位符参数
|
||||
ReceiverIds []uint64 // 接收人id
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package dto
|
||||
|
||||
import "mayfly-go/pkg/utils/anyx"
|
||||
|
||||
// ************** 系统消息 **************
|
||||
|
||||
const SuccessSysMsgType = 1
|
||||
const ErrorSysMsgType = 0
|
||||
const InfoSysMsgType = 2
|
||||
|
||||
// websocket消息
|
||||
type SysMsg struct {
|
||||
Type int `json:"type"` // 消息类型
|
||||
Category string `json:"category"` // 消息类别
|
||||
Title string `json:"title"` // 消息标题
|
||||
Msg string `json:"msg"` // 消息内容
|
||||
|
||||
ClientId string
|
||||
}
|
||||
|
||||
func (sm *SysMsg) WithTitle(title string) *SysMsg {
|
||||
sm.Title = title
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *SysMsg) WithCategory(category string) *SysMsg {
|
||||
sm.Category = category
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *SysMsg) WithMsg(msg any) *SysMsg {
|
||||
sm.Msg = anyx.ToString(msg)
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *SysMsg) WithClientId(clientId string) *SysMsg {
|
||||
sm.ClientId = clientId
|
||||
return sm
|
||||
}
|
||||
|
||||
// 普通消息
|
||||
func InfoSysMsg(title string, msg any) *SysMsg {
|
||||
return &SysMsg{Type: InfoSysMsgType, Title: title, Msg: anyx.ToString(msg)}
|
||||
}
|
||||
|
||||
// 成功消息
|
||||
func SuccessSysMsg(title string, msg any) *SysMsg {
|
||||
return &SysMsg{Type: SuccessSysMsgType, Title: title, Msg: anyx.ToString(msg)}
|
||||
}
|
||||
|
||||
// 错误消息
|
||||
func ErrSysMsg(title string, msg any) *SysMsg {
|
||||
return &SysMsg{Type: ErrorSysMsgType, Title: title, Msg: anyx.ToString(msg)}
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/internal/msg/domain/entity"
|
||||
"mayfly-go/internal/msg/domain/repository"
|
||||
"mayfly-go/internal/msg/msgx"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/ws"
|
||||
"time"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
)
|
||||
|
||||
type Msg interface {
|
||||
msgx.MsgSender
|
||||
|
||||
base.App[*entity.Msg]
|
||||
|
||||
GetPageList(condition *entity.Msg, pageParam model.PageParam, orderBy ...string) (*model.PageResult[*entity.Msg], error)
|
||||
|
||||
Create(ctx context.Context, msg *entity.Msg)
|
||||
|
||||
// 创建消息,并通过ws发送
|
||||
CreateAndSend(la *model.LoginAccount, msg *dto.SysMsg)
|
||||
}
|
||||
|
||||
var _ (Msg) = (*msgAppImpl)(nil)
|
||||
|
||||
type msgAppImpl struct {
|
||||
base.AppImpl[*entity.Msg, repository.Msg]
|
||||
|
||||
msgRepo repository.Msg `inject:"T"`
|
||||
}
|
||||
|
||||
@@ -29,13 +32,29 @@ func (a *msgAppImpl) GetPageList(condition *entity.Msg, pageParam model.PagePara
|
||||
return a.msgRepo.GetPageList(condition, pageParam)
|
||||
}
|
||||
|
||||
func (a *msgAppImpl) Create(ctx context.Context, msg *entity.Msg) {
|
||||
a.msgRepo.Insert(ctx, msg)
|
||||
}
|
||||
func (a *msgAppImpl) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
|
||||
// 存在i18n msgId,content则使用msgId翻译
|
||||
if msgId := msg.TmplExtra.GetInt("msgId"); msgId != 0 {
|
||||
msg.Content = i18n.TC(ctx, i18n.MsgId(msgId))
|
||||
}
|
||||
content, err := stringx.TemplateParse(msg.Content, msg.Params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *msgAppImpl) CreateAndSend(la *model.LoginAccount, wmsg *dto.SysMsg) {
|
||||
now := time.Now()
|
||||
msg := &entity.Msg{Type: 2, Msg: wmsg.Msg, RecipientId: int64(la.Id), CreateTime: &now, CreatorId: la.Id, Creator: la.Username}
|
||||
a.msgRepo.Insert(context.TODO(), msg)
|
||||
ws.SendJsonMsg(ws.UserId(la.Id), wmsg.ClientId, wmsg)
|
||||
for _, receiver := range msg.Receivers {
|
||||
msgEntity := &entity.Msg{
|
||||
Msg: content,
|
||||
RecipientId: int64(receiver.Id),
|
||||
Type: entity.MsgType(msg.TmplExtra.GetInt("type")),
|
||||
Subtype: entity.MsgSubtype(msg.TmplExtra.GetStr("subtype")),
|
||||
Status: cmp.Or(entity.MsgStatus(msg.TmplExtra.GetInt("status")), entity.MsgStatusRead),
|
||||
}
|
||||
msgEntity.Extra = msg.Params
|
||||
if err := a.Save(ctx, msgEntity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ type MsgTmpl interface {
|
||||
// Send 发送消息
|
||||
Send(ctx context.Context, tmplCode string, params map[string]any, receiverId ...uint64) error
|
||||
|
||||
SendMsg(ctx context.Context, mts *dto.MsgTmplSend) error
|
||||
|
||||
// DeleteTmplChannel 删除指定渠道关联的模板
|
||||
DeleteTmplChannel(ctx context.Context, channelId uint64) error
|
||||
}
|
||||
@@ -153,47 +155,52 @@ func (m *msgTmplAppImpl) Send(ctx context.Context, tmplCode string, params map[s
|
||||
return err
|
||||
}
|
||||
|
||||
// content, err := stringx.TemplateParse(tmpl.Tmpl, params)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// toAll := len(receiverId) == 0
|
||||
accounts, err := m.accountApp.GetByIds(receiverId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.SendMsg(ctx, &dto.MsgTmplSend{
|
||||
Tmpl: tmpl,
|
||||
Channels: channels,
|
||||
Params: params,
|
||||
ReceiverIds: receiverId,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *msgTmplAppImpl) SendMsg(ctx context.Context, mts *dto.MsgTmplSend) error {
|
||||
tmpl := mts.Tmpl
|
||||
msg := &msgx.Msg{
|
||||
Content: tmpl.Tmpl,
|
||||
Params: params,
|
||||
Params: mts.Params,
|
||||
Title: tmpl.Title,
|
||||
Type: tmpl.MsgType,
|
||||
ExtraData: tmpl.ExtraData,
|
||||
TmplExtra: tmpl.Extra,
|
||||
}
|
||||
|
||||
accounts, err := m.accountApp.GetByIds(mts.ReceiverIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(accounts) > 0 {
|
||||
msg.Receivers = collx.ArrayMap(accounts, func(account *sysentity.Account) msgx.Receiver {
|
||||
return msgx.Receiver{
|
||||
ExtraData: account.ExtraData,
|
||||
Id: account.Id,
|
||||
Extra: account.Extra,
|
||||
Email: account.Email,
|
||||
Mobile: account.Mobile,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
for _, channel := range mts.Channels {
|
||||
if channel.Status != entity.ChannelStatusEnable {
|
||||
logx.Warnf("channel is disabled => %s", channel.Code)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(channel *entity.MsgChannel) {
|
||||
if err := msgx.Send(&msgx.Channel{
|
||||
if err := msgx.Send(ctx, &msgx.Channel{
|
||||
Type: channel.Type,
|
||||
Name: channel.Name,
|
||||
URL: channel.Url,
|
||||
ExtraData: channel.ExtraData,
|
||||
Extra: channel.Extra,
|
||||
}, msg); err != nil {
|
||||
logx.Errorf("send msg error => channel=%s, msg=%s, err -> %v", channel.Code, msg.Content, err)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,15 @@ package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Msg struct {
|
||||
model.DeletedModel
|
||||
model.ExtraData
|
||||
model.CreateModel
|
||||
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
CreatorId uint64 `json:"creatorId"`
|
||||
Creator string `json:"creator" gorm:"size:50"`
|
||||
|
||||
Type int8 `json:"type"`
|
||||
Type MsgType `json:"type"` // 消息类型
|
||||
Subtype MsgSubtype `json:"subtype" gorm:"size:100"` // 消息子类型
|
||||
Status MsgStatus `json:"status"`
|
||||
Msg string `json:"msg" gorm:"size:2000"`
|
||||
RecipientId int64 `json:"recipientId"` // 接收人id,-1为所有接收
|
||||
}
|
||||
@@ -20,3 +18,33 @@ type Msg struct {
|
||||
func (a *Msg) TableName() string {
|
||||
return "t_sys_msg"
|
||||
}
|
||||
|
||||
type MsgType int8
|
||||
|
||||
const (
|
||||
MsgTypeNotify MsgType = 1 // 通知
|
||||
MsgTypeTodo MsgType = 2 // 代办
|
||||
)
|
||||
|
||||
type MsgSubtype string
|
||||
|
||||
const (
|
||||
// sys
|
||||
MsgSubtypeUserLogin MsgSubtype = "user.login"
|
||||
|
||||
// machine
|
||||
MsgSubtypeMachineFileUploadSuccess MsgSubtype = "machine.file.upload.success"
|
||||
MsgSubtypeMachineFileUploadFail MsgSubtype = "machine.file.upload.fail"
|
||||
|
||||
// db
|
||||
MsgSubtypeDbDumpFail MsgSubtype = "db.dump.fail"
|
||||
MsgSubtypeSqlScriptRunFail MsgSubtype = "db.sqlscript.run.fail"
|
||||
MsgSubtypeSqlScriptRunSuccess MsgSubtype = "db.sqlscript.run.success"
|
||||
)
|
||||
|
||||
type MsgStatus int8
|
||||
|
||||
const (
|
||||
MsgStatusRead MsgStatus = 1 // 已读
|
||||
MsgStatusUnRead MsgStatus = -1 // 未读
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user