8 Commits

Author SHA1 Message Date
meilin.huang
89e12678eb refactor: 引入dayjs、新增refreshToken无感刷新、团队新增有效期、数据库等问题修复 2024-05-13 19:55:43 +08:00
meilin.huang
137ebb8e9e fix: 数据库表新增数据表单全必填问题修复等 2024-05-11 12:09:55 +08:00
meilin.huang
05625bd8c1 feat: 1.8.3 2024-05-10 19:59:49 +08:00
meilin.huang
4afeac5fdd refactor: 代码优化与数据库表列显示配置优化 2024-05-09 21:29:34 +08:00
meilin.huang
1d0e91f1af refactor: 机器计划任务与流程定义关联至标签 2024-05-08 21:04:25 +08:00
Coder慌
cf5111a325 !118 修复空数组分隔异常
Merge pull request !118 from 蒋小小/N/A
2024-05-07 11:59:12 +00:00
meilin.huang
78957a8ebd refactor: base.repo与app重构优化 2024-05-05 14:53:30 +08:00
蒋小小
29fd5a25d2 修复空数组分隔异常
Signed-off-by: 蒋小小 <bwcx_jzy@163.com>
2024-04-20 17:33:19 +00:00
204 changed files with 2126 additions and 1764 deletions

View File

@@ -14,7 +14,7 @@ services:
restart: always
server:
image: mayfly-go:v1.3.1
image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.3
build:
context: .
dockerfile: Dockerfile

View File

@@ -15,6 +15,7 @@
"axios": "^1.6.2",
"clipboard": "^2.0.11",
"cropperjs": "^1.6.1",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"element-plus": "^2.7.2",
"js-base64": "^3.7.7",
@@ -33,7 +34,7 @@
"sql-formatter": "^15.0.2",
"trzsz": "^1.1.5",
"uuid": "^9.0.1",
"vue": "^3.4.25",
"vue": "^3.4.27",
"vue-router": "^4.3.2",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
@@ -48,15 +49,15 @@
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/compiler-sfc": "^3.4.25",
"@vue/compiler-sfc": "^3.4.27",
"code-inspector-plugin": "^0.4.5",
"dotenv": "^16.3.1",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.25.0",
"prettier": "^3.2.5",
"sass": "^1.75.0",
"sass": "^1.76.0",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vite": "^5.2.11",
"vue-eslint-parser": "^9.4.2"
},
"browserslist": [

View File

@@ -15,7 +15,7 @@ const config = {
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本
version: 'v1.8.2',
version: 'v1.8.4',
};
export default config;

View File

@@ -2,6 +2,7 @@ import request from './request';
export default {
login: (param: any) => request.post('/auth/accounts/login', param),
refreshToken: (param: any) => request.get('/auth/accounts/refreshToken', param),
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
getPublicKey: () => request.get('/common/public-key'),
getConfigValue: (params: any) => request.get('/sys/configs/value', params),

View File

@@ -38,6 +38,7 @@ export enum ResultEnum {
PARAM_ERROR = 405,
SERVER_ERROR = 500,
NO_PERMISSION = 501,
ACCESS_TOKEN_INVALID = 502, // accessToken失效
}
export const baseUrl: string = config.baseApiUrl;

View File

@@ -1,31 +0,0 @@
export function dateFormat2(fmt: string, date: Date) {
let ret;
const opt = {
'y+': date.getFullYear().toString(), // 年
'M+': (date.getMonth() + 1).toString(), // 月
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'm+': date.getMinutes().toString(), // 分
's+': date.getSeconds().toString(), // 秒
'S+': date.getMilliseconds() ? date.getMilliseconds().toString() : '', // 毫秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (const k in opt) {
ret = new RegExp('(' + k + ')').exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
}
}
return fmt;
}
export function dateStrFormat(fmt: string, dateStr: string) {
return dateFormat2(fmt, new Date(dateStr));
}
export function dateFormat(dateStr: string) {
if (!dateStr) {
return '';
}
return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
}

View File

@@ -1,3 +1,18 @@
import dayjs from 'dayjs';
/**
* 格式化日期
* @param date 日期 字符串 Date 时间戳等
* @param format 格式化格式 默认 YYYY-MM-DD HH:mm:ss
* @returns 格式化后内容
*/
export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
if (!date) {
return '';
}
return dayjs(date).format(format);
}
/**
* 格式化字节单位
* @param size byte size
@@ -46,110 +61,6 @@ export function convertToBytes(sizeStr: string) {
return bytes;
}
/*
* 年(Y) 可用1-4个占位符
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
* 星期(W) 可用1-3个占位符
* 季度(q为阿拉伯数字Q为中文数字)可用1或4个占位符
*
* let date = new Date()
* formatDate(date, "YYYY-mm-dd HH:MM:SS") // 2020-02-09 14:04:23
* formatDate(date, "YYYY-mm-dd HH:MM:SS Q") // 2020-02-09 14:09:03 一
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW") // 2020-02-09 14:45:12 星期日
* formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ") // 2020-02-09 14:09:36 第一季度
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-02-09 14:46:12 星期日 第一季度
*/
export function formatDate(date: Date, format: string) {
let we = date.getDay(); // 星期
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
const opt: any = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始要+1)
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
'S+': date.getSeconds().toString(), // 秒
'q+': qut, // 季度
};
// 中文数字 (星期)
const week: any = {
'0': '日',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六',
};
// 中文数字(季度)
const quarter: any = {
'1': '一',
'2': '二',
'3': '三',
'4': '四',
};
if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
for (let k in opt) {
let r = new RegExp('(' + k + ')').exec(format);
// 若输入的长度不为1则前面补零
if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
}
return format;
}
/**
* 10秒 10 * 1000
* 1分 60 * 1000
* 1小时 60 * 60 * 1000
* 24小时60 * 60 * 24 * 1000
* 3天 60 * 60* 24 * 1000 * 3
*
* let data = new Date()
* formatPast(data) // 刚刚
* formatPast(data - 11 * 1000) // 11秒前
* formatPast(data - 2 * 60 * 1000) // 2分钟前
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
* formatPast(data - 60 * 60 * 71 * 1000) // 2天前
* formatPast("2020-06-01") // 2020-06-01
* formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-06-01 08:00:00 星期一 第二季度
*/
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
// 传入格式处理、存储转换值
let t: any, s: any;
// 获取js 时间戳
let time: any = new Date().getTime();
// 是否是对象
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
// 当前时间戳 - 传入时间戳
time = Number.parseInt(`${time - t}`);
if (time < 10000) {
// 10秒内
return '刚刚';
} else if (time < 60000 && time >= 10000) {
// 超过10秒少于1分钟内
s = Math.floor(time / 1000);
return `${s}秒前`;
} else if (time < 3600000 && time >= 60000) {
// 超过1分钟少于1小时
s = Math.floor(time / 60000);
return `${s}分钟前`;
} else if (time < 86400000 && time >= 3600000) {
// 超过1小时少于24小时
s = Math.floor(time / 3600000);
return `${s}小时前`;
} else if (time < 259200000 && time >= 86400000) {
// 超过1天少于3天内
s = Math.floor(time / 86400000);
return `${s}天前`;
} else {
// 超过3天
let date = typeof param === 'string' || 'object' ? new Date(param) : param;
return formatDate(date, format);
}
}
/**
* 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
*

View File

@@ -1,6 +1,7 @@
import { randomUuid } from './string';
const TokenKey = 'm-token';
const RefreshTokenKey = 'm-refresh-token';
const UserKey = 'm-user';
const TagViewsKey = 'm-tagViews';
const ClientIdKey = 'm-clientId';
@@ -15,6 +16,14 @@ export function saveToken(token: string) {
setLocal(TokenKey, token);
}
export function getRefreshToken(): string {
return getLocal(RefreshTokenKey);
}
export function saveRefreshToken(refreshToken: string) {
return setLocal(RefreshTokenKey, refreshToken);
}
// 获取登录用户基础信息
export function getUser() {
return getLocal(UserKey);
@@ -39,6 +48,7 @@ export function getThemeConfig() {
export function clearUser() {
removeLocal(TokenKey);
removeLocal(UserKey);
removeLocal(RefreshTokenKey);
}
export function getTagViews() {

View File

@@ -1,5 +1,5 @@
import EnumValue from '@/common/Enum';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import { getValueByPath } from '@/common/utils/object';
import { getTextWidth } from '@/common/utils/string';
@@ -172,7 +172,7 @@ export class TableColumn {
*/
isTime(): TableColumn {
this.setFormatFunc((data: any, prop: string) => {
return dateFormat(getValueByPath(data, prop));
return formatDate(getValueByPath(data, prop));
});
return this;
}

View File

@@ -24,8 +24,33 @@
<SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
</div>
<clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
<el-dialog
v-if="!state.fullscreen"
destroy-on-close
:title="state.filesystemDialog.title"
v-model="state.filesystemDialog.visible"
:close-on-click-modal="false"
width="70%"
>
<machine-file
:machine-id="state.filesystemDialog.machineId"
:auth-cert-name="state.filesystemDialog.authCertName"
:protocol="state.filesystemDialog.protocol"
:file-id="state.filesystemDialog.fileId"
:path="state.filesystemDialog.path"
/>
</el-dialog>
</div>
<el-dialog destroy-on-close :title="state.filesystemDialog.title" v-model="state.filesystemDialog.visible" :close-on-click-modal="false" width="70%">
<el-dialog
v-if="!state.fullscreen"
destroy-on-close
:title="state.filesystemDialog.title"
v-model="state.filesystemDialog.visible"
:close-on-click-modal="false"
width="70%"
>
<machine-file
:machine-id="state.filesystemDialog.machineId"
:auth-cert-name="state.filesystemDialog.authCertName"

View File

@@ -1,5 +1,5 @@
import router from '@/router';
import { getClientId, getToken } from '@/common/utils/storage';
import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
import { templateResolve } from '@/common/utils/string';
import { ElMessage } from 'element-plus';
import { createFetch } from '@vueuse/core';
@@ -8,6 +8,7 @@ import { Result, ResultEnum } from '@/common/request';
import config from '@/common/config';
import { unref } from 'vue';
import { URL_401 } from '@/router/staticRouter';
import openApi from '@/common/openApi';
const baseUrl: string = config.baseApiUrl;
@@ -88,6 +89,18 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
return {
execute: async function () {
return execUaf(uaf);
},
isFetching: uaf.isFetching,
data: uaf.data,
abort: uaf.abort,
};
}
let refreshingToken = false;
let queue: any[] = [];
async function execUaf(uaf: any) {
try {
await uaf.execute(true);
} catch (e: any) {
@@ -119,14 +132,50 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
return Promise.reject(result);
}
const resultCode = result.code;
// 如果返回为成功结果则将结果的data赋值给响应式data
if (result.code === ResultEnum.SUCCESS) {
if (resultCode === ResultEnum.SUCCESS) {
uaf.data.value = result.data;
return;
}
// 如果是accessToken失效则使用refreshToken刷新token
if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
if (refreshingToken) {
// 请求加入队列等待, 防止并发多次请求refreshToken
return new Promise((resolve) => {
queue.push(() => {
resolve(execUaf(uaf));
});
});
}
try {
refreshingToken = true;
const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
saveToken(res.token);
saveRefreshToken(res.refresh_token);
// 重新缓存后端用户权限code
await openApi.getPermissions();
// 执行accessToken失效的请求
queue.forEach((resolve: any) => {
resolve();
});
} catch (e: any) {
clearUser();
} finally {
refreshingToken = false;
queue = [];
}
await execUaf(uaf);
return;
}
// 如果提示没有权限,则跳转至无权限页面
if (result.code === ResultEnum.NO_PERMISSION) {
if (resultCode === ResultEnum.NO_PERMISSION) {
router.push({
path: URL_401,
});
@@ -134,15 +183,10 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
}
// 如果返回的code不为成功则会返回对应的错误msg则直接统一通知即可。忽略登录超时或没有权限的提示直接跳转至401页面
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
ElMessage.error(result.msg);
uaf.error.value = new Error(result.msg);
}
return Promise.reject(result);
},
isFetching: uaf.isFetching,
data: uaf.data,
abort: uaf.abort,
};
}

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { dateFormat2 } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import { useUserInfo } from '@/store/userInfo';
import { getSysStyleConfig } from '@/common/sysconfig';
import { getLocal, getThemeConfig } from '@/common/utils/storage';
@@ -191,7 +191,7 @@ export const useThemeConfig = defineStore('themeConfig', {
},
// 设置水印时间为当前时间
setWatermarkNowTime() {
this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
this.themeConfig.watermarkText[1] = formatDate(new Date());
},
// 切换暗黑模式
switchDark(isDark: boolean) {

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
<el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
<template #header>
<DrawerHeader :header="title" :back="cancel" />
</template>
@@ -21,6 +21,10 @@
<el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
</el-form-item>
<el-form-item ref="tagSelectRef" prop="codePaths" label="关联资源">
<tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
</el-form-item>
<el-divider content-position="left">审批节点</el-divider>
<el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
@@ -70,6 +74,8 @@ import AccountSelectFormItem from '@/views/system/account/components/AccountSele
import Sortable from 'sortablejs';
import { randomUuid } from '../../common/utils/string';
import { ProcdefStatus } from './enums';
import TagTreeCheck from '../ops/component/TagTreeCheck.vue';
import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
data: {
@@ -115,6 +121,7 @@ const state = reactive({
remark: null,
// 流程的审批节点任务
tasks: '',
codePaths: [],
},
sortable: '' as any,
});
@@ -126,6 +133,7 @@ const { isFetching: saveBtnLoading, execute: saveFlowDefExec } = procdefApi.save
watch(props, (newValue: any) => {
if (newValue.data) {
state.form = { ...newValue.data };
state.form.codePaths = newValue.data.tags?.map((tag: any) => tag.codePath);
const tasks = JSON.parse(state.form.tasks);
tasks.forEach((t: any) => {
t.userId = Number.parseInt(t.userId);
@@ -160,11 +168,13 @@ const deleteTask = (idx: any) => {
};
const btnOk = async () => {
formRef.value.validate(async (valid: boolean) => {
if (!valid) {
ElMessage.error('表单填写有误');
try {
await formRef.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
const checkRes = checkTasks();
if (checkRes.err) {
ElMessage.error(checkRes.err);
@@ -178,7 +188,6 @@ const btnOk = async () => {
//重置表单域
formRef.value.resetFields();
state.form = {} as any;
});
};
const checkTasks = () => {

View File

@@ -18,6 +18,10 @@
<el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
</template>
<template #codePaths="{ data }">
<TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
</template>
<template #action="{ data }">
<el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
</template>
@@ -42,6 +46,7 @@ import { SearchItem } from '@/components/SearchForm';
import ProcdefEdit from './ProcdefEdit.vue';
import ProcdefTasks from './components/ProcdefTasks.vue';
import { ProcdefStatus } from './enums';
import TagCodePath from '../ops/component/TagCodePath.vue';
const perms = {
save: 'flow:procdef:save',
@@ -55,6 +60,7 @@ const columns = [
TableColumn.new('status', '状态').typeTag(ProcdefStatus),
TableColumn.new('remark', '备注'),
TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
TableColumn.new('codePaths', '关联资源').isSlot().setMinWidth('250px'),
TableColumn.new('creator', '创建账号'),
TableColumn.new('createTime', '创建时间').isTime(),
];

View File

@@ -17,11 +17,11 @@
<AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
<!-- {{ procinst.creator }} -->
</el-descriptions-item>
<el-descriptions-item label="发起时间">{{ dateFormat(procinst.createTime) }}</el-descriptions-item>
<el-descriptions-item label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
<div v-if="procinst.duration">
<el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ dateFormat(procinst.endTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
</div>
<el-descriptions-item label="流程状态">
@@ -86,11 +86,11 @@ import { procinstApi } from './api';
import { ElMessage } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
import { dateFormat } from '@/common/utils/date';
import ProcdefTasks from './components/ProcdefTasks.vue';
import { formatTime } from '@/common/utils/format';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
import { formatDate } from '@/common/utils/format';
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));

View File

@@ -2,7 +2,7 @@ import Api from '@/common/Api';
export const procdefApi = {
list: Api.newGet('/flow/procdefs'),
getByKey: Api.newGet('/flow/procdefs/{key}'),
getByResource: Api.newGet('/flow/procdefs/{resourceType}/{resourceCode}'),
save: Api.newPost('/flow/procdefs'),
del: Api.newDelete('/flow/procdefs/{id}'),
};

View File

@@ -3,7 +3,7 @@
<el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
<template #description>
<div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
<div v-if="task.completeTime">{{ `${dateFormat(task.completeTime)}` }}</div>
<div v-if="task.completeTime">{{ `${formatDate(task.completeTime)}` }}</div>
<div v-if="task.remark">{{ task.remark }}</div>
</template>
</el-step>
@@ -14,8 +14,7 @@
import { toRefs, reactive, watch, onMounted } from 'vue';
import { accountApi } from '../../system/api';
import { ProcinstTaskStatus } from '../enums';
import { dateFormat } from '@/common/utils/date';
import { procdefApi } from '../api';
import { formatDate } from '@/common/utils/format';
import { ElSteps, ElStep } from 'element-plus';
const props = defineProps({
@@ -23,8 +22,8 @@ const props = defineProps({
tasks: {
type: [String, Object],
},
procdefKey: {
type: String,
procdef: {
type: [Object],
},
// 流程实例任务列表
procinstTasks: {
@@ -54,7 +53,7 @@ watch(
);
watch(
() => props.procdefKey,
() => props.procdef,
async (newValue: any) => {
if (newValue) {
parseTasksByKey(newValue);
@@ -63,15 +62,14 @@ watch(
);
onMounted(() => {
if (props.procdefKey) {
parseTasksByKey(props.procdefKey);
if (props.procdef) {
parseTasksByKey(props.procdef);
return;
}
parseTasks(props.tasks);
});
const parseTasksByKey = async (key: string) => {
const procdef = await procdefApi.getByKey.request({ key });
const parseTasksByKey = async (procdef: any) => {
parseTasks(procdef.tasks);
};

View File

@@ -35,7 +35,7 @@
</el-col>
<el-col :xs="24" :sm="12" class="personal-item mb6">
<div class="personal-item-label">上次登录时间</div>
<div class="personal-item-value">{{ dateFormat(userInfo.lastLoginTime) }}</div>
<div class="personal-item-value">{{ formatDate(userInfo.lastLoginTime) }}</div>
</el-col>
</el-row>
</el-col>
@@ -84,7 +84,7 @@
<el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table-column prop="createTime" show-overflow-tooltip width="135">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
@@ -118,7 +118,7 @@
<el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
@@ -159,7 +159,7 @@
<el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
@@ -198,7 +198,7 @@
<el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
@@ -228,7 +228,7 @@
<el-table-column property="msg" label="消息"></el-table-column>
<el-table-column property="createTime" label="时间" width="150">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
@@ -257,7 +257,7 @@ import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '@/store/userInfo';
import { personApi } from '../personal/api';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import SvgIcon from '@/components/svgIcon/index.vue';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { resourceOpLogApi } from '../ops/tag/api';

View File

@@ -132,7 +132,7 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { initRouter } from '@/router/index';
import { saveToken, saveUser } from '@/common/utils/storage';
import { getRefreshToken, saveRefreshToken, saveToken, saveUser } from '@/common/utils/storage';
import { formatAxis } from '@/common/utils/format';
import openApi from '@/common/openApi';
import { RsaEncrypt } from '@/common/rsa';
@@ -279,19 +279,20 @@ const login = () => {
};
const otpVerify = async () => {
otpFormRef.value.validate(async (valid: boolean) => {
if (!valid) {
try {
await otpFormRef.value.validate();
} catch (e: any) {
return false;
}
try {
state.loading.otpConfirm = true;
const accessToken = await openApi.otpVerify(state.otpDialog.form);
await signInSuccess(accessToken);
const res = await openApi.otpVerify(state.otpDialog.form);
await signInSuccess(res.token, res.refresh_token);
state.otpDialog.visible = false;
} finally {
state.loading.otpConfirm = false;
}
});
};
// 登录
@@ -327,10 +328,12 @@ const onSignIn = async () => {
};
const updateUserInfo = async () => {
baseInfoFormRef.value.validate(async (valid: boolean) => {
if (!valid) {
try {
await baseInfoFormRef.value.validate();
} catch (e: any) {
return false;
}
try {
state.loading.updateUserConfirm = true;
const form = state.baseInfoDialog.form;
@@ -342,7 +345,6 @@ const updateUserInfo = async () => {
} finally {
state.loading.updateUserConfirm = false;
}
});
};
const loginResDeal = (loginRes: any) => {
@@ -366,7 +368,7 @@ const loginResDeal = (loginRes: any) => {
const token = loginRes.token;
// 如果不需要 otp校验则该token即为accessToken否则为otp校验token
if (loginRes.otp == -1) {
signInSuccess(token);
signInSuccess(token, loginRes.refresh_token);
return;
}
@@ -379,12 +381,16 @@ const loginResDeal = (loginRes: any) => {
};
// 登录成功后的跳转
const signInSuccess = async (accessToken: string = '') => {
const signInSuccess = async (accessToken: string = '', refreshToken = '') => {
if (!accessToken) {
accessToken = getToken();
}
if (!refreshToken) {
refreshToken = getRefreshToken();
}
// 存储 token 到浏览器缓存
saveToken(accessToken);
saveRefreshToken(refreshToken);
// 初始化路由
await initRouter();
@@ -415,11 +421,13 @@ const toIndex = async () => {
}, 300);
};
const changePwd = () => {
changePwdFormRef.value.validate(async (valid: boolean) => {
if (!valid) {
const changePwd = async () => {
try {
await changePwdFormRef.value.validate();
} catch (e: any) {
return false;
}
try {
state.loading.changePwd = true;
const form = state.changePwdDialog.form;
@@ -434,7 +442,6 @@ const changePwd = () => {
} finally {
state.loading.changePwd = false;
}
});
};
const cancelChangePwd = () => {

View File

@@ -35,8 +35,10 @@
</slot>
</span>
<span class="label-suffix">
<slot :node="node" :data="data" name="suffix"></slot>
</span>
</span>
</template>
</el-tree>
@@ -222,5 +224,13 @@ defineExpose({
display: inline-block;
min-width: 100%;
}
.label-suffix {
position: absolute;
right: 10px;
color: #c4c9c4;
font-size: 10px;
margin-top: 2px;
}
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<div class="w100" style="border: 1px solid var(--el-border-color)">
<el-input v-model="filterTag" clearable placeholder="输入关键字过滤" size="small" />
<div class="w100 tag-tree-check">
<el-input v-model="filterTag" @input="onFilterValChanged" clearable placeholder="输入关键字过滤" size="small" />
<div class="mt3" style="border: 1px solid var(--el-border-color)">
<el-scrollbar :style="{ height: props.height }">
<el-tree
v-bind="$attrs"
ref="tagTreeRef"
style="width: 100%"
:data="state.tags"
:default-expanded-keys="checkedTags"
:default-checked-keys="checkedTags"
@@ -24,7 +24,7 @@
:filter-node-method="filterNode"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span>
<SvgIcon
:name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon"
:color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.iconColor"
@@ -42,10 +42,11 @@
</el-tree>
</el-scrollbar>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import { tagApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumValue from '@/common/Enum';
@@ -56,7 +57,7 @@ const props = defineProps({
default: 'calc(100vh - 330px)',
},
tagType: {
type: Number,
type: [Number, Array<Number>],
default: TagResourceTypeEnum.Tag.value,
},
nodeKey: {
@@ -81,7 +82,12 @@ onMounted(() => {
});
const search = async () => {
state.tags = await tagApi.getTagTrees.request({ type: props.tagType });
let tagType: any = props.tagType;
if (Array.isArray(props.tagType)) {
tagType = props.tagType.join(',');
}
state.tags = await tagApi.getTagTrees.request({ type: tagType });
setTimeout(() => {
const checkedNodes = tagTreeRef.value.getCheckedNodes();
@@ -93,10 +99,6 @@ const search = async () => {
}, 200);
};
watch(filterTag, (val) => {
tagTreeRef.value!.filter(val);
});
const filterNode = (value: string, data: any) => {
if (!value) {
return true;
@@ -104,6 +106,10 @@ const filterNode = (value: string, data: any) => {
return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
};
const onFilterValChanged = (val: string) => {
tagTreeRef.value!.filter(val);
};
const tagTreeNodeCheck = (data: any) => {
const node = tagTreeRef.value.getNode(data.codePath);
console.log('check node: ', node);
@@ -150,4 +156,12 @@ const disableParentNodes = (node: any, disable = true) => {
disableParentNodes(node.parent, disable);
};
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.tag-tree-check {
.el-tree {
min-width: 100%;
// 横向滚动生效
display: inline-block;
}
}
</style>

View File

@@ -199,3 +199,16 @@ export function getTagTypeCodeByPath(codePath: string) {
return result;
}
export function expandCodePath(codePath: string) {
const parts = codePath.split('/');
const result = [];
let currentPath = '';
for (let i = 0; i < parts.length - 1; i++) {
currentPath += parts[i] + '/';
result.push(currentPath);
}
return result;
}

View File

@@ -62,8 +62,6 @@
<el-form-item prop="remark" label="备注">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<procdef-select-form-item v-model="form.flowProcdefKey" />
</el-form>
<template #footer>
@@ -80,9 +78,7 @@
import { toRefs, reactive, watch, ref, watchEffect } from 'vue';
import { dbApi } from './api';
import { ElMessage } from 'element-plus';
// import TagTreeSelect from '../component/TagTreeSelect.vue';
import type { CheckboxValueType } from 'element-plus';
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
import { DbType } from '@/views/ops/db/dialect';
import { ResourceCodePattern } from '@/common/pattern';
@@ -183,7 +179,6 @@ const state = reactive({
remark: '',
instanceId: null as any,
authCertName: '',
flowProcdefKey: '',
},
instances: [] as any,
});

View File

@@ -187,12 +187,10 @@
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" label="工单流程key">{{ infoDialog.data?.flowProcdefKey }}</el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data?.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data?.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data?.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
@@ -207,7 +205,7 @@ import { dbApi } from './api';
import config from '@/common/config';
import { joinClientParams } from '@/common/request';
import { isTrue } from '@/common/assert';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
@@ -240,7 +238,6 @@ const columns = ref([
TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
TableColumn.new('authCertName', '授权凭证'),
TableColumn.new('database', '库').isSlot().setMinWidth(80),
TableColumn.new('flowProcdefKey', '关联流程'),
TableColumn.new('remark', '备注'),
TableColumn.new('code', '编号'),
]);

View File

@@ -45,14 +45,14 @@
<el-descriptions :column="1" border>
<el-descriptions-item :span="1" label="数据库名称">{{ infoDialog.data.dbName }}</el-descriptions-item>
<el-descriptions-item v-if="infoDialog.data.pointInTime" :span="1" label="恢复时间点">{{
dateFormat(infoDialog.data.pointInTime)
formatDate(infoDialog.data.pointInTime)
}}</el-descriptions-item>
<el-descriptions-item v-if="!infoDialog.data.pointInTime" :span="1" label="数据库备份">{{
infoDialog.data.dbBackupHistoryName
}}</el-descriptions-item>
<el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
<el-descriptions-item :span="1" label="开始时间">{{ formatDate(infoDialog.data.startTime) }}</el-descriptions-item>
<el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
<el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
<el-descriptions-item :span="1" label="执行时间">{{ formatDate(infoDialog.data.lastTime) }}</el-descriptions-item>
<el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
@@ -66,7 +66,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { SearchItem } from '@/components/SearchForm';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
const pageTableRef: Ref<any> = ref(null);

View File

@@ -259,6 +259,7 @@ const handleSrcTableCheckChange = (data: { id: string; name: string }, checked:
}
}
if (data.id && (data.id + '').startsWith('list-item')) {
//
}
};

View File

@@ -26,7 +26,6 @@
</el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip min-width="120"> </el-table-column>
<el-table-column prop="flowProcdefKey" label="关联流程" min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column prop="code" label="编号" show-overflow-tooltip min-width="120"> </el-table-column>
<el-table-column min-wdith="120px">
<template #header>

View File

@@ -53,10 +53,10 @@
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
@@ -76,7 +76,7 @@
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dbApi } from './api';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';

View File

@@ -47,10 +47,8 @@
</template>
<template #suffix="{ data }">
<span class="db-table-size" v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
<span class="db-table-size" v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{
` ${data.params.dbTableSize}`
}}</span>
<span v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
<span v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{ ` ${data.params.dbTableSize}` }}</span>
</template>
</tag-tree>
</Pane>
@@ -148,7 +146,7 @@
:db-id="dt.params.id"
:db="dt.params.db"
:db-type="dt.params.type"
:flow-procdef-key="dt.params.flowProcdefKey"
:flow-procdef="dt.params.flowProcdef"
:height="state.tablesOpHeight"
/>
</el-tab-pane>
@@ -163,7 +161,7 @@
:dbId="tableCreateDialog.dbId"
:db="tableCreateDialog.db"
:dbType="tableCreateDialog.dbType"
:flow-procdef-key="tableCreateDialog.flowProcdefKey"
:flow-procdef="tableCreateDialog.flowProcdef"
:data="tableCreateDialog.data"
v-model:visible="tableCreateDialog.visible"
@submit-sql="onSubmitEditTableSql"
@@ -190,6 +188,7 @@ import { useEventListener } from '@vueuse/core';
import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
import { useAutoOpenResource } from '@/store/autoOpenResource';
import { storeToRefs } from 'pinia';
import { procdefApi } from '@/views/flow/api';
const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
@@ -243,7 +242,7 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
type: params.type,
tagPath: params.tagPath,
databases: params.dbs,
flowProcdefKey: params.flowProcdefKey,
flowProcdef: params.flowProcdef,
},
params.db
);
@@ -271,10 +270,11 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
.withContextMenuItems([ContextmenuItemRefresh]);
// 数据库实例节点类型
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params;
const dbs = params.database.split(' ')?.sort();
const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.DbName.value, resourceCode: params.code });
return dbs.map((x: any) => {
return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb)
.withParams({
@@ -285,7 +285,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
host: `${params.host}:${params.port}`,
dbs: dbs,
db: x,
flowProcdefKey: params.flowProcdefKey,
flowProcdef: flowProcdef,
})
.withIcon(DbIcon);
});
@@ -346,7 +346,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
])
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params;
let { id, db, type, flowProcdefKey, schema } = params;
let { id, db, type, flowProcdef, schema } = params;
// 获取当前库的所有表信息
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
state.reloadStatus = false;
@@ -362,7 +362,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
db,
type,
schema,
flowProcdefKey: flowProcdefKey,
flowProcdef: flowProcdef,
key: key,
parentKey: parentNode.key,
tableName: x.tableName,
@@ -448,7 +448,7 @@ const state = reactive({
dbId: 0,
db: '',
dbType: '',
flowProcdefKey: '',
flowProcdef: null as any,
data: {},
parentKey: '',
},
@@ -497,7 +497,7 @@ const autoOpenDb = (codePath: string) => {
// 置空
autoOpenResourceStore.setDbCodePath('');
tagTreeRef.value.setCurrentKey(dbCode);
}, 600);
}, 1000);
};
/**
@@ -669,6 +669,8 @@ const onTabChange = () => {
// 注册sql提示
registerDbCompletionItemProvider(nowTab.dbId, nowTab.db, nowTab.params.dbs, nowDbInst.value.type);
}
tagTreeRef.value.setCurrentKey(nowTab?.treeNodeKey);
};
const reloadSqls = (dbId: number, db: string) => {
@@ -700,7 +702,7 @@ const reloadNode = (nodeKey: string) => {
};
const onEditTable = async (data: any) => {
let { db, id, tableName, tableComment, type, parentKey, key, flowProcdefKey } = data.params;
let { db, id, tableName, tableComment, type, parentKey, key, flowProcdef } = data.params;
// data.label就是表名
if (tableName) {
state.tableCreateDialog.title = '修改表';
@@ -719,12 +721,12 @@ const onEditTable = async (data: any) => {
state.tableCreateDialog.dbId = id;
state.tableCreateDialog.db = db;
state.tableCreateDialog.dbType = type;
state.tableCreateDialog.flowProcdefKey = flowProcdefKey;
state.tableCreateDialog.flowProcdef = flowProcdef;
state.tableCreateDialog.visible = true;
};
const onDeleteTable = async (data: any) => {
let { db, id, tableName, parentKey, flowProcdefKey, schema } = data.params;
let { db, id, tableName, parentKey, flowProcdef, schema } = data.params;
await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -736,7 +738,7 @@ const onDeleteTable = async (data: any) => {
let schemaStr = schema ? `${dialect.quoteIdentifier(schema)}.` : '';
dbApi.sqlExec.request({ id, db, sql: `drop table ${schemaStr + dialect.quoteIdentifier(tableName)}` }).then(() => {
if (flowProcdefKey) {
if (flowProcdef) {
ElMessage.success('工单提交成功');
return;
}
@@ -748,7 +750,7 @@ const onDeleteTable = async (data: any) => {
};
const onRenameTable = async (data: any) => {
let { db, id, tableName, parentKey, flowProcdefKey } = data.params;
let { db, id, tableName, parentKey, flowProcdef } = data.params;
let tableData = { db, oldTableName: tableName, tableName };
let value = ref(tableName);
@@ -771,7 +773,7 @@ const onRenameTable = async (data: any) => {
dbId: id as any,
db: db as any,
dbType: nowDbInst.value.getDialect().getInfo().formatSqlDialect,
flowProcdefKey: flowProcdefKey,
flowProcdef: flowProcdef,
runSuccessCallback: () => {
setTimeout(() => {
parentKey && reloadNode(parentKey);
@@ -831,7 +833,7 @@ const getNowDbInfo = () => {
name: di.name,
type: di.type,
host: di.host,
flowProcdefKey: di.flowProcdefKey,
flowProcdef: di.flowProcdef,
dbName: state.db,
};
};
@@ -839,11 +841,6 @@ const getNowDbInfo = () => {
<style lang="scss">
.db-sql-exec {
.db-table-size {
color: #c4c9c4;
font-size: 9px;
}
.db-op {
height: calc(100vh - 106px);
}

View File

@@ -115,7 +115,7 @@
<el-option
v-for="item in state.targetColumnList"
:key="item.columnName"
:label="item.columnName + ` ${item.showDataType}` + (item.columnComment && ' - ' + item.columnComment)"
:label="item.columnName + ` ${item.columnType}` + (item.columnComment && ' - ' + item.columnComment)"
:value="item.columnName"
/>
</el-select>

View File

@@ -296,44 +296,37 @@ const onRunSql = async (newTab = false) => {
notBlank(sql && sql.trim(), '请选中需要执行的sql');
// 去除字符串前的空格、换行等
sql = sql.replace(/(^\s*)/g, '');
let execRemark = '';
let canRun = true;
// 简单截取前十个字符
const sqlPrefix = sql.slice(0, 10).toLowerCase();
if (
const nonQuery =
sqlPrefix.startsWith('update') ||
sqlPrefix.startsWith('insert') ||
sqlPrefix.startsWith('delete') ||
sqlPrefix.startsWith('alert') ||
sqlPrefix.startsWith('drop') ||
sqlPrefix.startsWith('create')
) {
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
inputErrorMessage: '请输入执行该sql的备注信息',
sqlPrefix.startsWith('create');
// 启用工单审批
if (nonQuery && getNowDbInst().flowProcdef) {
try {
getNowDbInst().promptExeSql(props.dbName, sql, null, () => {
ElMessage.success('工单提交成功');
});
execRemark = res.value;
if (!execRemark) {
canRun = false;
} catch (e) {
ElMessage.success('工单提交失败');
}
}
if (!canRun) {
return;
}
// 启用工单审批
if (execRemark && getNowDbInst().flowProcdefKey) {
try {
await getNowDbInst().runSql(props.dbName, sql, execRemark);
ElMessage.success('工单提交成功');
return;
} catch (e) {
ElMessage.success('工单提交失败');
return;
}
let execRemark;
if (nonQuery) {
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputErrorMessage: '输入执行该sql的备注信息',
});
execRemark = res.value;
}
let execRes: ExecResTab;

View File

@@ -6,7 +6,7 @@ export type SqlExecProps = {
dbId: number;
db: string;
dbType?: string;
flowProcdefKey?: string;
flowProcdef?: any;
runSuccessCallback?: Function;
cancelCallback?: Function;
};

View File

@@ -1,18 +1,18 @@
<template>
<div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" :close-on-click-modal="false">
<monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
<el-input
@keyup.enter="runSql"
ref="remarkInputRef"
v-model="remark"
:placeholder="props.flowProcdefKey ? '执行备注(必填)' : '执行备注(选填)'"
:placeholder="props.flowProcdef ? '执行备注(必填)' : '执行备注(选填)'"
class="mt5"
/>
<div v-if="props.flowProcdefKey">
<div v-if="props.flowProcdef">
<el-divider content-position="left">审批节点</el-divider>
<procdef-tasks :procdef-key="props.flowProcdefKey" />
<procdef-tasks :procdef="props.flowProcdef" />
</div>
<template #footer>
@@ -59,13 +59,15 @@ onMounted(() => {
*/
const runSql = async () => {
// 存在流程审批,则备注为必填
if (!state.remark && props.flowProcdefKey) {
if (!state.remark && props.flowProcdef) {
ElMessage.error('请输入执行的备注信息');
return;
}
try {
state.btnLoading = true;
runSuccess = true;
const res = await dbApi.sqlExec.request({
id: props.dbId,
db: props.db,
@@ -74,8 +76,7 @@ const runSql = async () => {
});
// 存在流程审批
if (props.flowProcdefKey) {
runSuccess = false;
if (props.flowProcdef) {
ElMessage.success('工单提交成功');
return;
}
@@ -87,7 +88,6 @@ const runSql = async () => {
}
}
runSuccess = true;
ElMessage.success('执行成功');
} catch (e) {
runSuccess = false;
@@ -96,9 +96,9 @@ const runSql = async () => {
if (props.runSuccessCallback) {
props.runSuccessCallback();
}
cancel();
}
state.btnLoading = false;
cancel();
}
};
@@ -113,7 +113,7 @@ const cancel = () => {
};
const open = () => {
state.sqlValue = sqlFormatter(props.sql, { language: props.dbType || 'mysql' });
state.sqlValue = sqlFormatter(props.sql, { language: (props.dbType || 'mysql') as any });
state.dialogVisible = true;
setTimeout(() => {
remarkInputRef.value?.focus();

View File

@@ -1,7 +1,6 @@
<template>
<div class="string-input-container w100" v-if="dataType == DataType.String">
<div class="string-input-container w100" v-if="dataType == DataType.String || dataType == DataType.Number">
<el-input
v-if="dataType == DataType.String"
:ref="(el: any) => focus && el?.focus()"
:disabled="disabled"
@blur="handleBlur"
@@ -13,18 +12,6 @@
<SvgIcon v-if="showEditorIcon" @mousedown="openEditor" class="string-input-container-icon" name="FullScreen" :size="10" />
</div>
<el-input
v-else-if="dataType == DataType.Number"
:ref="(el: any) => focus && el?.focus()"
:disabled="disabled"
@blur="handleBlur"
class="w100 mb4"
size="small"
v-model.number="itemValue"
:placeholder="placeholder"
type="number"
/>
<el-date-picker
v-else-if="dataType == DataType.Date"
:ref="(el: any) => focus && el?.focus()"
@@ -75,7 +62,7 @@
<script lang="ts" setup>
import { computed, ref, Ref } from 'vue';
import { ElInput } from 'element-plus';
import { ElInput, ElMessage } from 'element-plus';
import { DataType } from '../../dialect/index';
import SvgIcon from '@/components/svgIcon/index.vue';
import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
@@ -130,6 +117,10 @@ const handleBlur = () => {
if (editorOpening.value) {
return;
}
if (props.dataType == DataType.Number && itemValue.value && !/^-?\d*\.?\d+$/.test(itemValue.value)) {
ElMessage.error('输入内容与类型不匹配');
return;
}
emit('update:modelValue', itemValue.value);
emit('blur');
};

View File

@@ -36,7 +36,7 @@
<!-- 字段列的数据类型 -->
<div class="column-type">
<span v-if="column.dataTypeSubscript === 'icon-clock'">
<SvgIcon :size="10" name="Clock" style="cursor: unset" />
<SvgIcon :size="9" name="Clock" style="cursor: unset" />
</span>
<span class="font8" v-else>{{ column.dataTypeSubscript }}</span>
</div>
@@ -161,7 +161,7 @@ import { DbInst } from '@/views/ops/db/db';
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
import SvgIcon from '@/components/svgIcon/index.vue';
import { exportCsv, exportFile } from '@/common/utils/export';
import { dateStrFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import { useIntervalFn, useStorage } from '@vueuse/core';
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
import ColumnFormItem from './ColumnFormItem.vue';
@@ -476,9 +476,9 @@ const setTableColumns = (columns: any) => {
state.columns = columns.map((x: any) => {
const columnName = x.columnName;
// 数据类型
x.dataType = dbDialect.getDataType(x.dataType);
x.dataType = dbDialect.getDataType(x.columnType);
x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
x.remark = `${x.showDataType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
x.remark = `${x.columnType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
return {
...x,
@@ -617,6 +617,10 @@ const onDeleteData = async () => {
const db = state.db;
const dbInst = getNowDbInst();
dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
// 存在流程则恢复原值,需工单流程审批完后自动执行
if (dbInst.flowProcdef) {
return;
}
emits('dataDelete', deleteDatas);
});
};
@@ -628,7 +632,7 @@ const onEditRowData = () => {
return;
}
const data = selectionDatas[0];
state.tableDataFormDialog.data = data;
state.tableDataFormDialog.data = { ...data };
state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
state.tableDataFormDialog.visible = true;
};
@@ -674,13 +678,13 @@ const onExportCsv = () => {
columnNames.push(column.columnName);
}
}
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
exportCsv(`数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}`, columnNames, dataList);
};
const onExportSql = async () => {
const selectionDatas = state.datas;
exportFile(
`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}.sql`,
`数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}.sql`,
await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
);
};
@@ -763,7 +767,12 @@ const submitUpdateFields = async () => {
res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
}
dbInst.promptExeSql(db, res, cancelUpdateFields, () => {
dbInst.promptExeSql(db, res, null, () => {
// 存在流程则恢复原值,需工单流程审批完后自动执行
if (dbInst.flowProcdef) {
cancelUpdateFields();
return;
}
triggerRefresh();
cellUpdateMap.clear();
changeUpdatedField();
@@ -810,11 +819,11 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
switch (dataType) {
case DataType.Time:
return dateStrFormat('HH:mm:ss', originValue);
return formatDate(originValue, 'HH:mm:ss');
case DataType.Date:
return dateStrFormat('yyyy-MM-dd', originValue);
return formatDate(originValue, 'YYYY-MM-DD');
case DataType.DateTime:
return dateStrFormat('yyyy-MM-dd HH:mm:ss', originValue);
return formatDate(originValue, 'YYYY-MM-DD HH:mm:ss');
default:
return originValue;
}
@@ -880,8 +889,8 @@ defineExpose({
color: var(--el-color-info-light-3);
font-weight: bold;
position: absolute;
top: -5px;
padding: 2px;
top: -7px;
padding: 1px;
}
.column-right {

View File

@@ -6,10 +6,10 @@
:key="column.columnName"
class="w100 mb5"
:prop="column.columnName"
:required="column.nullable != 'YES' && !column.isPrimaryKey && !column.isIdentity"
:required="!column.nullable && !column.isPrimaryKey && !column.isIdentity"
>
<template #label>
<span class="pointer" :title="`${column.showDataType} | ${column.columnComment}`">
<span class="pointer" :title="`${column.columnType} | ${column.columnComment}`">
{{ column.columnName }}
</span>
</template>
@@ -17,7 +17,7 @@
<ColumnFormItem
v-model="modelValue[`${column.columnName}`]"
:data-type="dbInst.getDialect().getDataType(column.dataType)"
:placeholder="`${column.showDataType} ${column.columnComment}`"
:placeholder="`${column.columnType} ${column.columnComment}`"
:column-name="column.columnName"
:disabled="column.isIdentity"
/>
@@ -37,7 +37,6 @@ import { ref, watch, onMounted } from 'vue';
import ColumnFormItem from './ColumnFormItem.vue';
import { DbInst } from '../../db';
import { ElMessage } from 'element-plus';
import { getDbDialect } from '@/views/ops/db/dialect';
export interface ColumnFormItemProps {
dbInst: DbInst;
@@ -86,8 +85,9 @@ const closeDialog = () => {
};
const confirm = async () => {
dataForm.value.validate(async (valid: boolean) => {
if (!valid) {
try {
await dataForm.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写数据信息');
return false;
}
@@ -115,7 +115,6 @@ const confirm = async () => {
closeDialog();
emit('submitSuccess');
});
});
};
</script>

View File

@@ -12,16 +12,30 @@
width="auto"
title="表格字段配置"
trigger="click"
@hide="triggerCheckedColumns"
>
<div v-for="(item, index) in columns" :key="index">
<div><el-input v-model="checkedShowColumns.searchKey" size="small" placeholder="输入列名或备注过滤" /></div>
<div>
<el-checkbox
v-model="item.show"
v-model="checkedShowColumns.checkedAllColumn"
:indeterminate="checkedShowColumns.isIndeterminate"
@change="handleCheckAllColumnChange"
size="small"
>
选择所有
</el-checkbox>
<el-checkbox-group v-model="checkedShowColumns.columnNames" @change="handleCheckedColumnChange">
<div v-for="(item, index) in filterCheckedColumns" :key="index">
<el-checkbox
:key="index"
:label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
:true-value="true"
:false-value="false"
:value="item.columnName"
size="small"
/>
</div>
</el-checkbox-group>
</div>
<template #reference>
<el-link icon="Operation" size="small" :underline="false"></el-link>
</template>
@@ -36,17 +50,6 @@
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
<template #content>
1. 右击数据/表头可显示操作菜单 <br />
2. 按住Ctrl点击数据则为多选 <br />
3. 双击单元格可编辑数据 <br />
4. 鼠标悬停字段名或标签树的表名可提示相关备注
</template>
<el-link icon="QuestionFilled" :underline="false"> </el-link>
</el-tooltip>
<el-divider direction="vertical" border-style="dashed" />
<!-- 表数据展示配置 -->
<el-popover
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
@@ -98,7 +101,7 @@
<el-divider direction="vertical" />
<span style="color: var(--el-color-info-light-3)">
{{ item.showDataType }}
{{ item.columnType }}
<template v-if="item.columnComment">
<el-divider direction="vertical" />
@@ -329,9 +332,17 @@ const state = reactive({
tableHeight: '600px',
hasUpdatedFileds: false,
dbDialect: {} as DbDialect,
checkedShowColumns: {
searchKey: '',
checkedAllColumn: true,
isIndeterminate: false,
columnNames: [] as any,
},
});
const { datas, condition, loading, columns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } = toRefs(state);
const { datas, condition, loading, columns, checkedShowColumns, pageNum, pageSize, pageSizes, sql, hasUpdatedFileds, conditionDialog, addDataDialog } =
toRefs(state);
watch(
() => props.tableHeight,
@@ -351,6 +362,8 @@ onMounted(async () => {
state.dbDialect = getNowDbInst().getDialect();
useEventListener('click', handlerWindowClick);
state.checkedShowColumns.columnNames = state.columns.map((item: any) => item.columnName);
});
const handlerWindowClick = () => {
@@ -414,6 +427,7 @@ const handleSetPageNum = async () => {
state.pageNum = state.setPageNum;
await selectData();
};
const handleCount = async () => {
state.counting = true;
@@ -431,6 +445,24 @@ const handleCount = async () => {
state.counting = false;
};
const handleCheckAllColumnChange = (val: boolean) => {
state.checkedShowColumns.columnNames = val ? state.columns.map((x: any) => x.columnName) : [];
state.checkedShowColumns.isIndeterminate = false;
};
const handleCheckedColumnChange = (value: string[]) => {
const checkedCount = value.length;
state.checkedShowColumns.checkedAllColumn = checkedCount === state.columns.length;
state.checkedShowColumns.isIndeterminate = checkedCount > 0 && checkedCount < state.columns.length;
};
const triggerCheckedColumns = () => {
const checkedColumnNames = state.checkedShowColumns.columnNames;
for (let column of state.columns) {
column.show = checkedColumnNames.includes(column.columnName);
}
};
// 完整的条件,每次选中后会重置条件框内容,故需要这个变量在获取建议时将文本框内容保存
let completeCond = '';
// 是否存在列建议
@@ -490,16 +522,23 @@ const chooseCondColumnName = () => {
* 过滤条件列名
*/
const filterCondColumns = computed(() => {
return filterColumns(state.columnNameSearch);
});
const filterCheckedColumns = computed(() => {
return filterColumns(state.checkedShowColumns.searchKey);
});
const filterColumns = (searchKey: string) => {
const columns = state.columns;
let columnNameSearch = state.columnNameSearch;
if (!columnNameSearch) {
if (!searchKey) {
return columns;
}
columnNameSearch = columnNameSearch.toLowerCase();
searchKey = searchKey.toLowerCase();
return columns.filter((data: any) => {
return data.columnName.toLowerCase().includes(columnNameSearch) || data.columnComment.toLowerCase().includes(columnNameSearch);
});
return data.columnName.toLowerCase().includes(searchKey) || data.columnComment.toLowerCase().includes(searchKey);
});
};
/**
* 条件查询,点击列信息后显示输入对应的值
@@ -507,7 +546,7 @@ const filterCondColumns = computed(() => {
const onConditionRowClick = (event: any) => {
const row = event[0];
state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
state.conditionDialog.placeholder = `${row.showDataType} ${row.columnComment}`;
state.conditionDialog.placeholder = `${row.columnType} ${row.columnComment}`;
state.conditionDialog.columnRow = row;
state.conditionDialog.visible = true;
setTimeout(() => {

View File

@@ -152,8 +152,8 @@ const props = defineProps({
dbType: {
type: String,
},
flowProcdefKey: {
type: String,
flowProcdef: {
type: Object,
},
});
@@ -335,7 +335,7 @@ const submit = async () => {
dbId: props.dbId as any,
db: props.db as any,
dbType: dbDialect.getInfo().formatSqlDialect,
flowProcdefKey: props.flowProcdefKey,
flowProcdef: props.flowProcdef,
runSuccessCallback: () => {
emit('submit-sql', { tableName: state.tableData.tableName });
// cancel();

View File

@@ -1,7 +1,7 @@
<template>
<div class="db-table">
<el-row class="mb5">
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
<el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right">
<template #reference>
<el-button class="ml5" type="success" size="small">导出</el-button>
</template>
@@ -13,16 +13,15 @@
</el-radio-group>
</el-form-item>
<el-form-item label="导出表: ">
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tables">
<el-table-column type="selection" width="45" />
<el-form-item>
<el-table :data="state.dumpInfo.tables" empty-text="请先选择要导出的表" max-height="300" size="small">
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> </el-table-column>
</el-table>
</el-form-item>
<div style="text-align: right">
<el-button @click="showDumpInfo = false" size="small">取消</el-button>
<el-button @click="state.dumpInfo.visible = false" size="small">取消</el-button>
<el-button @click="dump(db)" type="success" size="small">确定</el-button>
</div>
</el-popover>
@@ -30,7 +29,9 @@
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
</el-row>
<el-table v-loading="loading" border stripe :data="filterTableInfos" size="small" :height="height">
<el-table v-loading="loading" @selection-change="handleDumpTableSelectionChange" border stripe :data="filterTableInfos" size="small" :height="height">
<el-table-column type="selection" width="30" />
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
<template #header>
<el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
@@ -82,7 +83,7 @@
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
<el-table border stripe :data="columnDialog.columns" size="small">
<el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
<el-table-column width="120" prop="showDataType" label="类型" show-overflow-tooltip> </el-table-column>
<el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
<el-table-column width="80" prop="nullable" label="是否可为空" show-overflow-tooltip> </el-table-column>
<el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
</el-table>
@@ -108,7 +109,7 @@
:dbId="dbId"
:db="db"
:dbType="dbType"
:flow-procdef-key="props.flowProcdefKey"
:flow-procdef="props.flowProcdef"
:data="tableCreateDialog.data"
v-model:visible="tableCreateDialog.visible"
@submit-sql="onSubmitSql"
@@ -150,8 +151,8 @@ const props = defineProps({
type: [String],
required: true,
},
flowProcdefKey: {
type: [String],
flowProcdef: {
type: [Object],
},
});
@@ -161,8 +162,8 @@ const state = reactive({
tables: [],
tableNameSearch: '',
tableCommentSearch: '',
showDumpInfo: false,
dumpInfo: {
visible: false,
id: 0,
db: '',
type: 3,
@@ -201,19 +202,7 @@ const state = reactive({
},
});
const {
loading,
tables,
tableNameSearch,
tableCommentSearch,
showDumpInfo,
dumpInfo,
chooseTableName,
columnDialog,
indexDialog,
ddlDialog,
tableCreateDialog,
} = toRefs(state);
const { loading, tableNameSearch, tableCommentSearch, dumpInfo, chooseTableName, columnDialog, indexDialog, ddlDialog, tableCreateDialog } = toRefs(state);
onMounted(async () => {
getTables();
@@ -259,21 +248,22 @@ const getTables = async () => {
* 选择导出数据库表
*/
const handleDumpTableSelectionChange = (vals: any) => {
state.dumpInfo.tables = vals.map((x: any) => x.tableName);
state.dumpInfo.tables = vals;
};
/**
* 数据库信息导出
*/
const dump = (db: string) => {
isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
const tableNames = state.dumpInfo.tables.map((x: any) => x.tableName);
const a = document.createElement('a');
a.setAttribute(
'href',
`${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&${joinClientParams()}`
`${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${tableNames.join(',')}&${joinClientParams()}`
);
a.click();
state.showDumpInfo = false;
state.dumpInfo.visible = false;
};
const showColumns = async (row: any) => {
@@ -327,7 +317,7 @@ const dropTable = async (row: any) => {
sql: `DROP TABLE ${tableName}`,
dbId: props.dbId as any,
db: props.db as any,
flowProcdefKey: props.flowProcdefKey,
flowProcdef: props.flowProcdef,
runSuccessCallback: async () => {
await getTables();
},

View File

@@ -41,9 +41,9 @@ export class DbInst {
type: string;
/**
* 流程定义key,若存在则需要审批执行
* 流程定义,若存在则需要审批执行
*/
flowProcdefKey: string;
flowProcdef: any;
/**
* dbName -> db
@@ -359,7 +359,7 @@ export class DbInst {
dbType: this.getDialect().getInfo().formatSqlDialect,
runSuccessCallback: successFunc,
cancelCallback: cancelFunc,
flowProcdefKey: this.flowProcdefKey,
flowProcdef: this.flowProcdef,
});
};
@@ -383,6 +383,11 @@ export class DbInst {
}
let dbInst = dbInstCache.get(inst.id);
if (dbInst) {
// 更新可能更改的流程定义
if (inst.flowProcdef !== undefined) {
dbInst.flowProcdef = inst.flowProcdef;
dbInstCache.set(dbInst.id, dbInst);
}
return dbInst;
}
console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
@@ -393,7 +398,7 @@ export class DbInst {
dbInst.name = inst.name;
dbInst.type = inst.type;
dbInst.databases = inst.databases;
dbInst.flowProcdefKey = inst.flowProcdefKey;
dbInst.flowProcdef = inst.flowProcdef;
dbInstCache.set(dbInst.id, dbInst);
return dbInst;
@@ -477,17 +482,17 @@ export class DbInst {
}
for (let col of columns) {
if (col.charMaxLength > 0) {
col.showDataType = `${col.dataType}(${col.charMaxLength})`;
col.columnType = `${col.dataType}(${col.charMaxLength})`;
col.showLength = col.charMaxLength;
col.showScale = null;
continue;
}
if (col.numPrecision > 0) {
if (col.numScale > 0) {
col.showDataType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
col.columnType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
col.showScale = col.numScale;
} else {
col.showDataType = `${col.dataType}(${col.numPrecision})`;
col.columnType = `${col.dataType}(${col.numPrecision})`;
col.showScale = null;
}
@@ -495,7 +500,7 @@ export class DbInst {
continue;
}
col.showDataType = col.dataType;
col.columnType = col.dataType;
}
}
}

View File

@@ -160,10 +160,10 @@
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
@@ -236,12 +236,11 @@ import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue
import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getMachineTerminalSocketUrl, machineApi } from './api';
import { dateFormat } from '@/common/utils/date';
import ResourceTags from '../component/ResourceTags.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import { formatByteSize } from '@/common/utils/format';
import { formatByteSize, formatDate } from '@/common/utils/format';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { SearchItem } from '@/components/SearchForm';
import { getTagPathSearchItem } from '../component/tag';

View File

@@ -25,7 +25,7 @@
</template>
<template #suffix="{ data }">
<span style="color: #c4c9c4; font-size: 9px" v-if="data.type.value == MachineNodeType.AuthCert">{{
<span v-if="data.type.value == MachineNodeType.AuthCert">{{
` ${data.params.selectAuthCert.username}@${data.params.ip}:${data.params.port}`
}}</span>
</template>
@@ -103,10 +103,10 @@
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
@@ -157,7 +157,7 @@
import { defineAsyncComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
import { useRouter } from 'vue-router';
import { getMachineTerminalSocketUrl, machineApi } from './api';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import { hasPerms } from '@/components/auth/auth';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
@@ -368,7 +368,7 @@ const autoOpenTerminal = (codePath: string) => {
const acNode = tagTreeRef.value.getNode(authCertName);
openTerminal(acNode.data.params);
}, 600);
}, 1000);
};
const openTerminal = (machine: any, ex?: boolean) => {
@@ -402,14 +402,14 @@ const openTerminal = (machine: any, ex?: boolean) => {
}
}
let { name, username } = machine;
let { name } = machine;
const labelName = `${machine.selectAuthCert.username}@${name}`;
// 同一个机器的终端打开多次key后添加下划线和数字区分
openIds[ac] = openIds[ac] ? ++openIds[ac] : 1;
let sameIndex = openIds[ac];
let key = `${ac}_${username}_${sameIndex}`;
let key = `${ac}_${sameIndex}`;
// 只保留name的15个字超出部分只保留前后10个字符中间用省略号代替
const label = labelName.length > 15 ? labelName.slice(0, 10) + '...' + labelName.slice(-10) : labelName;
@@ -537,6 +537,9 @@ const onResizeTagTree = () => {
const onTabChange = () => {
fitTerminal();
const nowTab = state.tabs.get(state.activeTermName);
tagTreeRef.value.setCurrentKey(nowTab?.authCert);
};
const fitTerminal = () => {

View File

@@ -33,7 +33,7 @@
<el-table-column prop="cmd" label="命令" show-overflow-tooltip min-width="150px"> </el-table-column>
<el-table-column prop="time" label="执行时间" min-width="80" show-overflow-tooltip>
<template #default="scope">
{{ dateFormat(new Date(scope.row.time * 1000).toString()) }}
{{ formatDate(new Date(scope.row.time * 1000).toString()) }}
</template>
</el-table-column>
</el-table>
@@ -48,7 +48,7 @@ import * as AsciinemaPlayer from 'asciinema-player';
import 'asciinema-player/dist/bundle/asciinema-player.css';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
const props = defineProps({
visible: { type: Boolean },
@@ -122,8 +122,8 @@ const playRec = async (rec: any) => {
idleTimeLimit: 2,
// fit: false,
// terminalFontSize: 'small',
cols: 144,
rows: 32,
// cols: 144,
// rows: 32,
});
});
} finally {

View File

@@ -1,14 +1,18 @@
<template>
<div class="mock-data-dialog">
<el-dialog
<el-drawer
:title="title"
v-model="dialogVisible"
:close-on-click-modal="false"
:before-close="cancel"
:show-close="true"
:destroy-on-close="true"
width="900px"
size="40%"
>
<template #header>
<DrawerHeader :header="title" :back="cancel" />
</template>
<el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
<el-form-item prop="name" label="名称">
<el-input v-model="form.name" placeholder="请输入名称"></el-input>
@@ -34,19 +38,13 @@
<el-input v-model="form.remark" placeholder="请输入备注"></el-input>
</el-form-item>
<el-form-item prop="machineIds" label="关联机器">
<el-select multiple v-model="form.machineIds" filterable placeholder="请选关联机器" style="width: 100%">
<el-option v-for="ac in state.machines" :key="ac.id" :value="ac.id" :label="ac.ip">
{{ ac.ip }}
<el-divider direction="vertical" border-style="dashed" />
{{ ac.tagPath }}{{ ac.name }}
</el-option>
</el-select>
</el-form-item>
<el-form-item prop="script" label="执行脚本" required>
<monaco-editor style="width: 100%" v-model="form.script" language="shell" height="300px"
<monaco-editor style="width: 100%" v-model="form.script" language="shell" height="200px"
/></el-form-item>
<el-form-item ref="tagSelectRef" prop="codePaths" label="关联机器">
<tag-tree-check height="200px" :tag-type="TagResourceTypeEnum.Machine.value" v-model="form.codePaths" />
</el-form-item>
</el-form>
<template #footer>
@@ -55,7 +53,7 @@
<el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk" :disabled="submitDisabled"> </el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -67,6 +65,9 @@ import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
import { notEmpty } from '@/common/assert';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import CrontabInput from '@/components/crontab/CrontabInput.vue';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import TagTreeCheck from '../../component/TagTreeCheck.vue';
import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
visible: {
@@ -130,11 +131,11 @@ const state = reactive({
id: null,
name: '',
cron: '',
machineIds: [],
remark: '',
script: '',
status: 1,
saveExecResType: -1,
codePaths: [],
},
machines: [] as any,
btnLoading: false,
@@ -154,7 +155,7 @@ watch(props, async (newValue: any) => {
}
if (newValue.data) {
state.form = { ...newValue.data };
state.form.machineIds = await cronJobApi.relateMachineIds.request({ cronJobId: state.form.id });
state.form.codePaths = newValue.data.tags?.map((tag: any) => tag.codePath);
} else {
state.form = { script: '', status: 1 } as any;
state.chooseMachines = [];

View File

@@ -19,6 +19,10 @@
<el-tag v-else type="danger" effect="plain">未运行</el-tag>
</template>
<template #codePaths="{ data }">
<TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
</template>
<template #action="{ data }">
<el-button :disabled="data.status == CronJobStatusEnum.Disable.value" v-auth="perms.saveCronJob" type="primary" @click="runCronJob(data)" link
>执行</el-button
@@ -41,6 +45,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
import { SearchItem } from '@/components/SearchForm';
import TagCodePath from '../../component/TagCodePath.vue';
const CronJobEdit = defineAsyncComponent(() => import('./CronJobEdit.vue'));
const CronJobExecList = defineAsyncComponent(() => import('./CronJobExecList.vue'));
@@ -61,6 +66,7 @@ const columns = ref([
TableColumn.new('running', '运行状态').isSlot(),
TableColumn.new('saveExecResType', '记录类型').typeTag(CronJobSaveExecResTypeEnum),
TableColumn.new('remark', '备注'),
TableColumn.new('codePaths', '关联机器').isSlot().setMinWidth('250px'),
TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter(),
]);

View File

@@ -9,7 +9,7 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="codePaths" label="关联机器" min-width="220px" show-overflow-tooltip>
<el-table-column prop="codePaths" label="关联机器" min-width="250px" show-overflow-tooltip>
<template #default="scope">
<TagCodePath :path="scope.row.tags.map((tag: any) => tag.codePath)" />
</template>

View File

@@ -36,13 +36,8 @@
/>
</template>
<template #label="{ data }">
<span v-if="data.type.value == MongoNodeType.Dbs">
{{ data.params.database }}
<span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
</span>
<span v-else>{{ data.label }}</span>
<template #suffix="{ data }">
<span v-if="data.type.value == MongoNodeType.Dbs">{{ formatByteSize(data.params.size) }}</span>
</template>
</tag-tree>
</Pane>

View File

@@ -35,6 +35,10 @@
<SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
</template>
<template #suffix="{ data }">
<span v-if="data.type.value == RedisNodeType.Db">{{ data.params.keys }}</span>
</template>
</tag-tree>
</Pane>
@@ -197,6 +201,7 @@ import { Splitpanes, Pane } from 'splitpanes';
import { RedisInst } from './redis';
import { useAutoOpenResource } from '@/store/autoOpenResource';
import { storeToRefs } from 'pinia';
import { procdefApi } from '@/views/flow/api';
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
@@ -244,11 +249,13 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
// redis实例节点类型
const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const redisInfo = parentNode.params;
const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.Redis.value, resourceCode: redisInfo.code });
let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
return new TagTreeNode(x, `db${x}`, NodeTypeDb).withIsLeaf(true).withParams({
id: redisInfo.id,
db: x,
flowProcdefKey: redisInfo.flowProcdefKey,
flowProcdef: flowProcdef,
name: `db${x}`,
keys: 0,
});
@@ -268,7 +275,7 @@ const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async
}
// 替换label
dbs.forEach((e: any) => {
e.label = `${e.params.name} [${e.params.keys}]`;
e.label = `${e.params.name}`;
});
return dbs;
});
@@ -281,7 +288,7 @@ const NodeTypeDb = new NodeType(RedisNodeType.Db).withNodeClickFunc((nodeData: T
redisInst.value.id = nodeData.params.id;
redisInst.value.db = Number.parseInt(nodeData.params.db);
redisInst.value.flowProcdefKey = nodeData.params.flowProcdefKey;
redisInst.value.flowProcdef = nodeData.params.flowProcdef;
scan();
});

View File

@@ -58,13 +58,6 @@
placeholder="请输入密码, 修改操作可不填"
autocomplete="new-password"
>
<!-- <template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template>
</el-popover>
</template> -->
</el-input>
</el-form-item>
<el-form-item prop="db" label="库号" required>
@@ -84,8 +77,6 @@
<el-form-item prop="remark" label="备注">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<procdef-select-form-item v-model="form.flowProcdefKey" />
</el-tab-pane>
<el-tab-pane label="其他配置" name="other">
@@ -108,12 +99,11 @@
</template>
<script lang="ts" setup>
import { toRefs, reactive, ref, watchEffect } from 'vue';
import { toRefs, reactive, ref, watch } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import TagTreeSelect from '../component/TagTreeSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
import { ResourceCodePattern } from '@/common/pattern';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
@@ -199,7 +189,6 @@ const state = reactive({
db: '',
remark: '',
sshTunnelMachineId: -1,
flowProcdefKey: '',
},
submitForm: {} as any,
dbList: [0],
@@ -211,7 +200,9 @@ const { dialogVisible, tabActiveName, form, submitForm, dbList } = toRefs(state)
const { isFetching: testConnBtnLoading, execute: testConnExec } = redisApi.testConn.useApi(submitForm);
const { isFetching: saveBtnLoading, execute: saveRedisExec } = redisApi.saveRedis.useApi(submitForm);
watchEffect(() => {
watch(
() => props.visible,
() => {
state.dialogVisible = props.visible;
if (!state.dialogVisible) {
return;
@@ -224,10 +215,10 @@ watchEffect(() => {
convertDb(state.form.db);
} else {
state.form = { db: '0', tagCodePaths: [] } as any;
state.dbList = [0];
}
});
}
);
const convertDb = (db: string) => {
state.dbList = db.split(',').map((x) => Number.parseInt(x));

View File

@@ -128,13 +128,12 @@
<el-descriptions-item :span="3" label="库">{{ detailDialog.data.db }}</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" label="工单流程key">{{ detailDialog.data?.flowProcdefKey }}</el-descriptions-item>
<el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(detailDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(detailDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ detailDialog.data.creator }}</el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(detailDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(detailDialog.data.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="修改者">{{ detailDialog.data.modifier }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
@@ -154,7 +153,7 @@ import { redisApi } from './api';
import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import ResourceTags from '../component/ResourceTags.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
@@ -180,7 +179,6 @@ const columns = ref([
TableColumn.new('name', '名称'),
TableColumn.new('host', 'host:port'),
TableColumn.new('mode', 'mode'),
TableColumn.new('flowProcdefKey', '关联流程'),
TableColumn.new('remark', '备注'),
TableColumn.new('code', '编号'),
TableColumn.new('action', '操作').isSlot().setMinWidth(200).fixedRight().alignCenter(),

View File

@@ -5,7 +5,7 @@ export type CmdExecProps = {
id: number;
db: number | string;
cmd: any[];
flowProcdefKey?: string;
flowProcdef?: any;
visible?: boolean;
runSuccessFn?: Function;
cancelFn?: Function;

View File

@@ -4,9 +4,9 @@
<el-input type="textarea" disabled v-model="state.cmdStr" class="mt5" rows="5" />
<el-input @keyup.enter="runCmd" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
<div v-if="props.flowProcdefKey">
<div v-if="props.flowProcdef">
<el-divider content-position="left">审批节点</el-divider>
<procdef-tasks :procdef-key="props.flowProcdefKey" />
<procdef-tasks :procdef="props.flowProcdef" />
</div>
<template #footer>
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<CmdExecProps>(), {});
const remarkInputRef = ref<InputInstance>();
const state = reactive({
dialogVisible: false,
flowProcdefKey: '' as any,
flowProcdef: null as any,
cmdStr: '',
remark: '',
btnLoading: false,

View File

@@ -13,9 +13,9 @@ export class RedisInst {
db: number;
/**
* 流程定义key,若存在则需要审批执行
* 流程定义,若存在则需要审批执行
*/
flowProcdefKey: string;
flowProcdef: any;
/**
* 执行命令
@@ -24,11 +24,11 @@ export class RedisInst {
*/
async runCmd(cmd: any[]) {
// 工单流程定义存在,并且为写入命令时,弹窗输入工单相关信息并提交
if (this.flowProcdefKey && writeCmd[cmd[0].toUpperCase()]) {
if (this.flowProcdef && writeCmd[cmd[0].toUpperCase()]) {
showCmdExecBox({
id: this.id,
db: this.db,
flowProcdefKey: this.flowProcdefKey,
flowProcdef: this.flowProcdef,
cmd,
});
// 报错,阻止后续继续执行

View File

@@ -82,9 +82,9 @@
<el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
<el-descriptions-item label="创建者">{{ currentTag.creator }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ dateFormat(currentTag.createTime) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(currentTag.createTime) }}</el-descriptions-item>
<el-descriptions-item label="修改者">{{ currentTag.modifier }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ dateFormat(currentTag.updateTime) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDate(currentTag.updateTime) }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
@@ -145,22 +145,23 @@
</template>
<script lang="ts" setup>
import { toRefs, ref, watch, reactive, onMounted, Ref } from 'vue';
import { toRefs, ref, watch, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from './api';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
import { useUserInfo } from '@/store/userInfo';
import { Splitpanes, Pane } from 'splitpanes';
import MachineList from '../machine/MachineList.vue';
import RedisList from '../redis/RedisList.vue';
import MongoList from '../mongo/MongoList.vue';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import EnumValue from '@/common/Enum';
import InstanceList from '../db/InstanceList.vue';
import TagCodePath from '../component/TagCodePath.vue';
const MachineList = defineAsyncComponent(() => import('../machine/MachineList.vue'));
const InstanceList = defineAsyncComponent(() => import('../db/InstanceList.vue'));
const RedisList = defineAsyncComponent(() => import('../redis/RedisList.vue'));
const MongoList = defineAsyncComponent(() => import('../mongo/MongoList.vue'));
interface Tree {
id: number;
codePath: string;
@@ -195,6 +196,9 @@ const contextmenuAdd = new ContextmenuItem('addTag', '添加子标签')
const contextmenuEdit = new ContextmenuItem('edit', '编辑')
.withIcon('edit')
.withPermission('tag:save')
.withHideFunc((data: any) => {
return data.type != TagResourceTypeEnum.Tag.value;
})
.withOnClick((data: any) => showEditTagDialog(data));
const contextmenuDel = new ContextmenuItem('delete', '删除')
@@ -376,6 +380,11 @@ const search = async () => {
state.data = res;
};
const getDetail = async (id: number) => {
const tags = await tagApi.listByQuery.request({ id });
return tags?.[0];
};
// 树节点右击事件
const nodeContextmenu = (event: any, data: any) => {
const { clientX, clientY } = event;
@@ -384,8 +393,8 @@ const nodeContextmenu = (event: any, data: any) => {
contextmenuRef.value.openContextmenu(data);
};
const treeNodeClick = (data: any) => {
state.currentTag = data;
const treeNodeClick = async (data: any) => {
state.currentTag = await getDetail(data.id);
// 关闭可能存在的右击菜单
contextmenuRef.value.closeContextmenu();
};

View File

@@ -18,6 +18,8 @@
<TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
</template>
<template #validityDate="{ data }"> {{ data.validityStartDate }} ~ {{ data.validityEndDate }} </template>
<template #action="{ data }">
<el-button @click.prevent="showMembers(data)" link type="primary">成员</el-button>
@@ -36,16 +38,30 @@
<DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
</template>
<el-form ref="teamForm" :model="addTeamDialog.form" label-width="auto">
<el-form ref="teamForm" :model="addTeamDialog.form" :rules="teamFormRules" label-width="auto">
<el-form-item prop="name" label="团队名" required>
<el-input :disabled="addTeamDialog.form.id" v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="validityDate" label="生效时间" required>
<el-date-picker
v-model="addTeamDialog.form.validityDate"
type="datetimerange"
start-placeholder="生效开始时间"
end-placeholder="生效结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
date-format="YYYY-MM-DD"
time-format="HH:mm:ss"
/>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="tag" label="标签">
<TagTreeCheck v-model="state.addTeamDialog.form.codePaths" :tag-type="0" />
<TagTreeCheck height="calc(100vh - 390px)" v-model="state.addTeamDialog.form.codePaths" :tag-type="0" />
</el-form-item>
</el-form>
<template #footer>
@@ -101,16 +117,35 @@ import AccountSelectFormItem from '@/views/system/account/components/AccountSele
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import TagTreeCheck from '../component/TagTreeCheck.vue';
import TagCodePath from '../component/TagCodePath.vue';
import { formatDate } from '@/common/utils/format';
const teamForm: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const showMemPageTableRef: Ref<any> = ref(null);
const teamFormRules = {
name: [
{
required: true,
message: '请输入团队名',
trigger: ['change', 'blur'],
},
],
validityDate: [
{
required: true,
message: '请选择生效时间',
trigger: ['change', 'blur'],
},
],
};
const searchItems = [SearchItem.input('name', '团队名称')];
const columns = [
TableColumn.new('name', '团队名称'),
TableColumn.new('remark', '备注'),
TableColumn.new('tags', '分配标签').isSlot().setAddWidth(40),
TableColumn.new('validityDate', '有效期').isSlot('validityDate').setMinWidth(310),
TableColumn.new('remark', '备注'),
TableColumn.new('creator', '创建者'),
TableColumn.new('createTime', '创建时间').isTime(),
TableColumn.new('modifier', '修改者'),
@@ -122,7 +157,7 @@ const state = reactive({
currentEditPermissions: false,
addTeamDialog: {
visible: false,
form: { id: 0, name: '', remark: '', codePaths: [] },
form: { id: 0, name: '', validityDate: ['', ''], validityStartDate: '', validityEndDate: '', remark: '', codePaths: [] },
},
query: {
pageNum: 1,
@@ -172,24 +207,33 @@ const showSaveTeamDialog = async (data: any) => {
if (data) {
state.addTeamDialog.form.id = data.id;
state.addTeamDialog.form.name = data.name;
state.addTeamDialog.form.validityDate = [data.validityStartDate, data.validityEndDate];
state.addTeamDialog.form.remark = data.remark;
state.addTeamDialog.form.codePaths = data.tags?.map((tag: any) => tag.codePath);
// state.addTeamDialog.form.tags = await tagApi.getRelateTagIds.request({ relateType: TagTreeRelateTypeEnum.Team.value, relateId: data.id });
} else {
let end = new Date();
end.setFullYear(end.getFullYear() + 10);
state.addTeamDialog.form.validityDate = [formatDate(new Date()), formatDate(end)];
}
state.addTeamDialog.visible = true;
};
const saveTeam = async () => {
teamForm.value.validate(async (valid: any) => {
if (valid) {
try {
await teamForm.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
const form = state.addTeamDialog.form;
form.validityStartDate = form.validityDate[0];
form.validityEndDate = form.validityDate[1];
await tagApi.saveTeam.request(form);
ElMessage.success('保存成功');
search();
cancelSaveTeam();
}
});
};
const cancelSaveTeam = () => {

View File

@@ -114,9 +114,10 @@ watchEffect(() => {
});
const btnOk = async () => {
accountForm.value.validate(async (valid: boolean) => {
if (!valid) {
ElMessage.error('表单填写有误');
try {
await accountForm.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
@@ -126,7 +127,6 @@ const btnOk = async () => {
//重置表单域
accountForm.value.resetFields();
state.form = {} as any;
});
};
const cancel = () => {

View File

@@ -42,7 +42,7 @@
<el-table-column property="creator" label="分配账号" width="125"></el-table-column>
<el-table-column property="createTime" label="分配时间">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
{{ formatDate(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
@@ -60,7 +60,7 @@ import AccountEdit from './AccountEdit.vue';
import { AccountStatusEnum } from '../enums';
import { accountApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="900px" :destroy-on-close="true">
<el-form ref="configForm" :model="form" label-width="auto">
<el-form ref="configForm" :model="form" :rules="rules" label-width="auto">
<el-form-item prop="name" label="配置项" required>
<el-input v-model="form.name"></el-input>
</el-form-item>
@@ -44,6 +44,24 @@
import { ref, toRefs, reactive, watch, watchEffect } from 'vue';
import { configApi, accountApi } from '../api';
import { DynamicFormEdit } from '@/components/dynamic-form';
import { ElMessage } from 'element-plus';
const rules = {
name: [
{
required: true,
message: '请输入配置项',
trigger: ['change', 'blur'],
},
],
key: [
{
required: true,
message: '请输入配置key',
trigger: ['change', 'blur'],
},
],
};
const props = defineProps({
visible: {
@@ -125,8 +143,13 @@ const getAccount = (username: any) => {
};
const btnOk = async () => {
configForm.value.validate(async (valid: boolean) => {
if (valid) {
try {
await configForm.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
if (state.params) {
state.form.params = JSON.stringify(state.params);
}
@@ -139,8 +162,6 @@ const btnOk = async () => {
await saveConfigExec();
emit('val-change', state.form);
cancel();
}
});
};
</script>
<style lang="scss"></style>

View File

@@ -254,7 +254,14 @@ const changeLinkType = () => {
state.form.meta.component = '';
};
const btnOk = () => {
const btnOk = async () => {
try {
await menuForm.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
const submitForm = { ...state.form };
if (submitForm.type == 1) {
// 如果是菜单则解析meta如果值为false或者''则去除该值
@@ -263,16 +270,12 @@ const btnOk = () => {
submitForm.meta = null as any;
}
menuForm.value.validate(async (valid: any) => {
if (valid) {
state.submitForm = submitForm;
await saveResouceExec();
emit('val-change', submitForm);
ElMessage.success('保存成功');
cancel();
}
});
};
const parseMenuMeta = (meta: any) => {
@@ -314,10 +317,4 @@ const cancel = () => {
emit('cancel');
};
</script>
<style lang="scss">
// .m-dialog {
// .el-cascader {
// width: 100%;
// }
// }
</style>
<style lang="scss"></style>

View File

@@ -89,9 +89,9 @@
</el-descriptions-item>
<el-descriptions-item label="创建者">{{ currentResource.creator }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ dateFormat(currentResource.createTime) }} </el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(currentResource.createTime) }} </el-descriptions-item>
<el-descriptions-item label="修改者">{{ currentResource.modifier }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ dateFormat(currentResource.updateTime) }} </el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDate(currentResource.updateTime) }} </el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
@@ -119,7 +119,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import ResourceEdit from './ResourceEdit.vue';
import { ResourceTypeEnum } from '../enums';
import { resourceApi } from '../api';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
import { Splitpanes, Pane } from 'splitpanes';

View File

@@ -1,7 +1,7 @@
<template>
<div class="role-dialog">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
<el-form ref="roleForm" :model="form" label-width="auto">
<el-form ref="roleForm" :model="form" :rules="rules" label-width="auto">
<el-form-item prop="name" label="角色名称" required>
<el-input v-model="form.name" auto-complete="off"></el-input>
</el-form-item>
@@ -32,6 +32,30 @@ import { ref, toRefs, reactive, watchEffect } from 'vue';
import { roleApi } from '../api';
import { RoleStatusEnum } from '../enums';
const rules = {
name: [
{
required: true,
message: '请输入角色名称',
trigger: ['change', 'blur'],
},
],
code: [
{
required: true,
message: '请输入角色编号',
trigger: ['change', 'blur'],
},
],
status: [
{
required: true,
message: '请选择状态',
trigger: ['change', 'blur'],
},
],
};
const props = defineProps({
visible: {
type: Boolean,
@@ -80,13 +104,15 @@ const cancel = () => {
};
const btnOk = async () => {
roleForm.value.validate(async (valid: boolean) => {
if (valid) {
try {
await roleForm.value.validate();
} catch (e: any) {
return false;
}
await saveRoleExec();
emit('val-change', state.form);
cancel();
}
});
};
</script>
<style lang="scss"></style>

View File

@@ -20,7 +20,7 @@
{{ data.creator }}
</el-descriptions-item>
<el-descriptions-item label="分配时间">
{{ dateFormat(data.createTime) }}
{{ formatDate(data.createTime) }}
</el-descriptions-item>
</el-descriptions>
</template>
@@ -35,7 +35,7 @@
<script lang="ts" setup>
import { toRefs, reactive, watch } from 'vue';
import { ResourceTypeEnum } from '../enums';
import { dateFormat } from '@/common/utils/date';
import { formatDate } from '@/common/utils/format';
const props = defineProps({
visible: {

View File

@@ -11,9 +11,11 @@ server:
cert-file: ./default.pem
jwt:
# jwt key不设置默认使用随机字符串
key:
# 过期时间单位分钟
expire-time: 1440
key: 333333000000
# accessToken过期时间单位分钟
expire-time: 720
# refreshToken过期时间单位分钟
refresh-token-expire-time: 4320
# 资源密码aes加密key
aes:
key: 1111111111111111

View File

@@ -7,13 +7,13 @@ require (
gitee.com/liuzongyang/libpq v1.0.9
github.com/buger/jsonparser v1.1.1
github.com/emirpasic/gods v1.18.1
github.com/gin-gonic/gin v1.9.1
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/go-gormigrate/gormigrate/v2 v2.1.0
github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.14.0
github.com/go-playground/validator/v10 v10.20.0
github.com/go-sql-driver/mysql v1.8.1
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
@@ -28,12 +28,12 @@ require (
github.com/pquerna/otp v1.4.0
github.com/redis/go-redis/v9 v9.5.1
github.com/robfig/cron/v3 v3.0.1 //
github.com/sijms/go-ora/v2 v2.8.13
github.com/stretchr/testify v1.8.4
github.com/sijms/go-ora/v2 v2.8.17
github.com/stretchr/testify v1.9.0
github.com/veops/go-ansiterm v0.0.5
go.mongodb.org/mongo-driver v1.15.0 // mongo
golang.org/x/crypto v0.22.0 // ssh
golang.org/x/oauth2 v0.19.0
golang.org/x/crypto v0.23.0 // ssh
golang.org/x/oauth2 v0.20.0
golang.org/x/sync v0.7.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
@@ -46,13 +46,15 @@ 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.9.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // 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.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // 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
@@ -67,36 +69,36 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // 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.15 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // 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.3 // indirect
github.com/spf13/pflag v1.0.5 // 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.11 // indirect
github.com/ugorji/go/codec v1.2.12 // 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-20181117223130-1be2e3e5546d // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
google.golang.org/grpc v1.52.3 // indirect
google.golang.org/protobuf v1.31.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

View File

@@ -2,7 +2,6 @@ package api
import (
"context"
"encoding/json"
"fmt"
"mayfly-go/internal/auth/api/form"
"mayfly-go/internal/auth/config"
@@ -75,6 +74,7 @@ type OtpVerifyInfo struct {
Username string
OptStatus int
AccessToken string
RefreshToken string
OtpSecret string
}
@@ -84,10 +84,9 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
req.BindJsonAndValid(rc, otpVerify)
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
otpInfoJson := cache.GetStr(tokenKey)
biz.NotEmpty(otpInfoJson, "otpToken错误或失效, 请重新登陆获取")
otpInfo := new(OtpVerifyInfo)
json.Unmarshal([]byte(otpInfoJson), otpInfo)
ok := cache.Get(tokenKey, otpInfo)
biz.IsTrue(ok, "otpToken错误或失效, 请重新登陆获取")
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
failCount := cache.GetInt(failCountKey)
@@ -116,7 +115,20 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
go saveLogin(la, getIpAndRegion(rc))
cache.Del(tokenKey)
rc.ResData = accessToken
rc.ResData = collx.Kvs("token", accessToken, "refresh_token", otpInfo.RefreshToken)
}
func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
refreshToken := rc.Query("refresh_token")
biz.NotEmpty(refreshToken, "refresh_token不能为空")
accountId, username, err := req.ParseToken(refreshToken)
if err != nil {
panic(errorx.PermissionErr)
}
token, refreshToken, err := req.CreateToken(accountId, username)
biz.ErrIsNil(err)
rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken)
}
func (a *AccountLogin) Logout(rc *req.Ctx) {

View File

@@ -41,18 +41,19 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
// 默认为不校验otp
otpStatus := OtpStatusNone
// 访问系统使用的token
accessToken, err := req.CreateToken(account.Id, username)
accessToken, refreshToken, err := req.CreateToken(account.Id, username)
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
// 若系统配置中设置开启otp双因素校验则进行otp校验
if accountLoginSecurity.UseOtp {
otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken)
otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken, refreshToken)
otpStatus = otpInfo.OptStatus
if otpurl != "" {
res["otpUrl"] = otpurl
}
accessToken = otpToken
} else {
res["refresh_token"] = refreshToken
// 不进行otp二次校验则直接返回accessToken
// 保存登录消息
go saveLogin(account, loginIp)
@@ -64,7 +65,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
return res
}
func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVerifyInfo, string, string) {
func useOtp(account *sysentity.Account, otpIssuer, accessToken string, refreshToken string) (*OtpVerifyInfo, string, string) {
biz.ErrIsNil(account.OtpSecretDecrypt())
otpSecret := account.OtpSecret
// 修改状态为已注册
@@ -83,13 +84,14 @@ func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVeri
otpUrl = key.URL()
otpSecret = key.Secret()
}
// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
// 缓存otpInfo, 只有双因素校验通过才可返回真正的token
otpInfo := &OtpVerifyInfo{
AccountId: account.Id,
Username: account.Username,
OptStatus: otpStatus,
OtpSecret: otpSecret,
AccessToken: accessToken,
RefreshToken: refreshToken,
}
cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
return otpInfo, otpUrl, token

View File

@@ -171,7 +171,7 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
}
// 进行登录
account, err := a.AccountApp.GetById(new(sysentity.Account), accountId, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
account, err := a.AccountApp.GetById(accountId, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
clientIp := getIpAndRegion(rc)

View File

@@ -26,6 +26,8 @@ func Init(router *gin.RouterGroup) {
// 用户账号密码登录
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSave("用户登录")).DontNeedToken(),
req.NewGet("/accounts/refreshToken", accountLogin.RefreshToken).DontNeedToken(),
// 用户退出登录
req.NewPost("/accounts/logout", accountLogin.Logout),

View File

@@ -5,10 +5,10 @@ import (
"encoding/base64"
"fmt"
"io"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/api/form"
"mayfly-go/internal/db/api/vo"
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/application/dto"
"mayfly-go/internal/db/config"
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
@@ -84,8 +84,6 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
dbId := cast.ToUint64(v)
biz.NotBlank(dbId, "存在错误dbId")
biz.ErrIsNil(d.DbApp.Delete(ctx, dbId))
// 删除该库的sql执行记录
d.DbSqlExecApp.DeleteBy(ctx, &entity.DbSqlExec{DbId: dbId})
}
}
@@ -97,9 +95,9 @@ func (d *Db) ExecSql(rc *req.Ctx) {
dbId := getDbId(rc)
dbConn, err := d.DbApp.GetDbConn(dbId, form.Db)
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%s")
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, dbConn.Info.TagPath[0])
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, dbConn.Info.CodePath[0])
sqlBytes, err := base64.StdEncoding.DecodeString(form.Sql)
biz.ErrIsNilAppendErr(err, "sql解码失败: %s")
@@ -174,10 +172,8 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
clientId := rc.Query("clientId")
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
// 开启流程审批时,执行文件暂时还未处理
biz.IsTrue(dbConn.Info.FlowProcdefKey == "", "该库已开启流程审批,暂不支持该操作")
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%s")
rc.ReqParam = fmt.Sprintf("filename: %s -> %s", filename, dbConn.Info.GetLogDesc())
defer func() {
@@ -245,7 +241,7 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
}
dbConn, err = d.DbApp.GetDbConn(dbId, stmtUse.DBName.String())
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(laId, dbConn.Info.TagPath...), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(laId, dbConn.Info.CodePath...), "%s")
execReq.DbConn = dbConn
}
// 需要记录执行记录
@@ -282,12 +278,12 @@ func (d *Db) DumpSql(rc *req.Ctx) {
needData := dumpType == "2" || dumpType == "3"
la := rc.GetLoginAccount()
db, err := d.DbApp.GetById(new(entity.Db), dbId)
biz.ErrIsNil(err, "该数据库不存在")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, d.TagApp.ListTagPathByTypeAndCode(consts.ResourceTypeDb, db.Code)...), "%s")
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, dbConn.Info.CodePath...), "%s")
now := time.Now()
filename := fmt.Sprintf("%s-%s.%s.sql%s", db.Name, dbName, now.Format("20060102150405"), extName)
filename := fmt.Sprintf("%s-%s.%s.sql%s", dbConn.Info.Name, dbName, now.Format("20060102150405"), extName)
rc.Header("Content-Type", "application/octet-stream")
rc.Header("Content-Disposition", "attachment; filename="+filename)
if extName != ".gz" {
@@ -308,7 +304,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
}
}()
biz.ErrIsNil(d.DbApp.DumpDb(rc.MetaCtx, &application.DumpDbReq{
biz.ErrIsNil(d.DbApp.DumpDb(rc.MetaCtx, &dto.DumpDb{
DbId: dbId,
DbName: dbName,
Tables: tables,
@@ -317,7 +313,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
Writer: rc.GetWriter(),
}))
rc.ReqParam = collx.Kvs("db", db, "database", dbName, "tables", tablesStr, "dumpType", dumpType)
rc.ReqParam = collx.Kvs("db", dbConn.Info, "database", dbName, "tables", tablesStr, "dumpType", dumpType)
}
func (d *Db) TableInfos(rc *req.Ctx) {

View File

@@ -27,7 +27,7 @@ type DbBackup struct {
func (d *DbBackup) GetPageList(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
db, err := d.dbApp.GetById(dbId)
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
queryCond, page := req.BindQueryAndPage[*entity.DbBackupQuery](rc, new(entity.DbBackupQuery))
@@ -49,7 +49,7 @@ func (d *DbBackup) Create(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instanceId")
db, err := d.dbApp.GetById(dbId)
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
jobs := make([]*entity.DbBackup, 0, len(dbNames))
for _, dbName := range dbNames {
@@ -134,7 +134,7 @@ func (d *DbBackup) Start(rc *req.Ctx) {
// @router /api/dbs/:dbId/db-names-without-backup [GET]
func (d *DbBackup) GetDbNamesWithoutBackup(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instance_id", "database")
db, err := d.dbApp.GetById(dbId, "instance_id", "database")
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
dbNames := strings.Fields(db.Database)
dbNamesWithoutBackup, err := d.backupApp.GetDbNamesWithoutBackup(db.InstanceId, dbNames)
@@ -147,7 +147,7 @@ func (d *DbBackup) GetDbNamesWithoutBackup(rc *req.Ctx) {
func (d *DbBackup) GetHistoryPageList(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
db, err := d.dbApp.GetById(dbId, "instance_id", "database")
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
backupHistoryCond, page := req.BindQueryAndPage[*entity.DbBackupHistoryQuery](rc, new(entity.DbBackupHistoryQuery))

View File

@@ -64,7 +64,7 @@ func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
_ = d.DataSyncTaskApp.UpdateById(rc.MetaCtx, task)
if task.Status == entity.DataSyncTaskStatusEnable {
task, err := d.DataSyncTaskApp.GetById(new(entity.DataSyncTask), task.Id)
task, err := d.DataSyncTaskApp.GetById(task.Id)
biz.ErrIsNil(err, "该任务不存在")
d.DataSyncTaskApp.AddCronJob(rc.MetaCtx, task)
} else {
@@ -92,7 +92,7 @@ func (d *DataSyncTask) Stop(rc *req.Ctx) {
func (d *DataSyncTask) GetTask(rc *req.Ctx) {
taskId := d.getTaskId(rc)
dbEntity, _ := d.DataSyncTaskApp.GetById(new(entity.DataSyncTask), taskId)
dbEntity, _ := d.DataSyncTaskApp.GetById(taskId)
rc.ResData = dbEntity
}

View File

@@ -5,6 +5,7 @@ import (
"mayfly-go/internal/db/api/form"
"mayfly-go/internal/db/api/vo"
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/application/dto"
"mayfly-go/internal/db/domain/entity"
tagapp "mayfly-go/internal/tag/application"
@@ -70,7 +71,7 @@ func (d *Instance) SaveInstance(rc *req.Ctx) {
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
rc.ReqParam = form
id, err := d.InstanceApp.SaveDbInstance(rc.MetaCtx, &application.SaveDbInstanceParam{
id, err := d.InstanceApp.SaveDbInstance(rc.MetaCtx, &dto.SaveDbInstance{
DbInstance: instance,
AuthCerts: form.AuthCerts,
TagCodePaths: form.TagCodePaths,
@@ -83,7 +84,7 @@ func (d *Instance) SaveInstance(rc *req.Ctx) {
// @router /api/instances/:instance [GET]
func (d *Instance) GetInstance(rc *req.Ctx) {
dbId := getInstanceId(rc)
dbEntity, err := d.InstanceApp.GetById(new(entity.DbInstance), dbId)
dbEntity, err := d.InstanceApp.GetById(dbId)
biz.ErrIsNil(err, "获取数据库实例错误")
rc.ResData = dbEntity
}

View File

@@ -22,7 +22,7 @@ type DbRestore struct {
func (d *DbRestore) GetPageList(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
db, err := d.dbApp.GetById(dbId, "db_instance_id", "database")
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
var restores []vo.DbRestore
@@ -43,7 +43,7 @@ func (d *DbRestore) Create(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instanceId")
db, err := d.dbApp.GetById(dbId, "instanceId")
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
job := &entity.DbRestore{
@@ -123,7 +123,7 @@ func (d *DbRestore) Disable(rc *req.Ctx) {
// @router /api/dbs/:dbId/db-names-without-backup [GET]
func (d *DbRestore) GetDbNamesWithoutRestore(rc *req.Ctx) {
dbId := uint64(rc.PathParamInt("dbId"))
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instance_id", "database")
db, err := d.dbApp.GetById(dbId, "instance_id", "database")
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
dbNames := strings.Fields(db.Database)
dbNamesWithoutRestore, err := d.restoreApp.GetDbNamesWithoutRestore(db.InstanceId, dbNames)

View File

@@ -43,8 +43,7 @@ func (d *DbSql) GetSqlNames(rc *req.Ctx) {
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: 1, DbId: dbId, Db: dbName}
dbSql.CreatorId = rc.GetLoginAccount().Id
var sqls []entity.DbSql
d.DbSqlApp.ListByCond(model.NewModelCond(dbSql).Columns("id", "name"), &sqls)
sqls, _ := d.DbSqlApp.ListByCond(model.NewModelCond(dbSql).Columns("id", "name"))
rc.ResData = sqls
}

View File

@@ -21,8 +21,6 @@ type DbListVO struct {
Host string `json:"host"`
Port int `json:"port"`
FlowProcdefKey string `json:"flowProcdefKey"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`

View File

@@ -3,11 +3,13 @@ package application
import (
"context"
"fmt"
"mayfly-go/internal/db/application/dto"
"mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/biz"
@@ -40,7 +42,7 @@ type Db interface {
GetDbConnByInstanceId(instanceId uint64) (*dbi.DbConn, error)
// DumpDb dumpDb
DumpDb(ctx context.Context, reqParam *DumpDbReq) error
DumpDb(ctx context.Context, reqParam *dto.DumpDb) error
}
type dbAppImpl struct {
@@ -48,10 +50,13 @@ type dbAppImpl struct {
dbSqlRepo repository.DbSql `inject:"DbSqlRepo"`
dbInstanceApp Instance `inject:"DbInstanceApp"`
dbSqlExecApp DbSqlExec `inject:"DbSqlExecApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
}
var _ (Db) = (*dbAppImpl)(nil)
// 注入DbRepo
func (d *dbAppImpl) InjectDbRepo(repo repository.Db) {
d.Repo = repo
@@ -85,8 +90,8 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
return d.Insert(ctx, dbEntity)
}, func(ctx context.Context) error {
// 将库关联至指定数据库授权凭证下
return d.tagApp.RelateTagsByCodeAndType(ctx, &tagapp.RelateTagsByCodeAndTypeParam{
Tags: []*tagapp.ResourceTag{{
return d.tagApp.RelateTagsByCodeAndType(ctx, &tagdto.RelateTagsByCodeAndType{
Tags: []*tagdto.ResourceTag{{
Code: dbEntity.Code,
Type: tagentity.TagTypeDbName,
Name: dbEntity.Name,
@@ -103,7 +108,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
}
dbId := dbEntity.Id
old, err := d.GetById(new(entity.Db), dbId)
old, err := d.GetById(dbId)
if err != nil {
return errorx.NewBiz("该数据库不存在")
}
@@ -142,7 +147,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
}
func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
db, err := d.GetById(new(entity.Db), id)
db, err := d.GetById(id)
if err != nil {
return errorx.NewBiz("该数据库不存在")
}
@@ -160,7 +165,9 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
// 删除该库下用户保存的所有sql信息
return d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: id})
}, func(ctx context.Context) error {
return d.tagApp.DeleteTagByParam(ctx, &tagapp.DelResourceTagParam{
return d.dbSqlExecApp.DeleteBy(ctx, &entity.DbSqlExec{DbId: id})
}, func(ctx context.Context) error {
return d.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
ResourceCode: db.Code,
ResourceType: tagentity.TagTypeDbName,
})
@@ -169,12 +176,12 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
return dbm.GetDbConn(dbId, dbName, func() (*dbi.DbInfo, error) {
db, err := d.GetById(new(entity.Db), dbId)
db, err := d.GetById(dbId)
if err != nil {
return nil, errorx.NewBiz("数据库信息不存在")
}
instance, err := d.dbInstanceApp.GetById(new(entity.DbInstance), db.InstanceId)
instance, err := d.dbInstanceApp.GetById(db.InstanceId)
if err != nil {
return nil, errorx.NewBiz("数据库实例不存在")
}
@@ -183,13 +190,9 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
if err != nil {
return nil, err
}
di.TagPath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDbName), db.Code)
di.CodePath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDbName), db.Code)
di.Id = db.Id
if db.FlowProcdefKey != nil {
di.FlowProcdefKey = *db.FlowProcdefKey
}
checkDb := di.GetDatabase()
if !strings.Contains(" "+db.Database+" ", " "+checkDb+" ") {
return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName)
@@ -205,9 +208,8 @@ func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbi.DbConn, error
return conn, nil
}
var dbs []*entity.Db
if err := d.ListByCond(model.NewModelCond(&entity.Db{InstanceId: instanceId}).Columns("id", "database"), &dbs); err != nil {
dbs, err := d.ListByCond(&entity.Db{InstanceId: instanceId}, "id", "database")
if err != nil {
return nil, errorx.NewBiz("获取数据库列表失败")
}
if len(dbs) == 0 {
@@ -219,7 +221,7 @@ func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbi.DbConn, error
return d.GetDbConn(firstDb.Id, strings.Split(firstDb.Database, " ")[0])
}
func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *DumpDbReq) error {
func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
writer := newGzipWriter(reqParam.Writer)
defer writer.Close()
dbId := reqParam.DbId

View File

@@ -63,21 +63,18 @@ func (app *DbBackupApp) Init() error {
}
func (app *DbBackupApp) prune(ctx context.Context) error {
var jobs []*entity.DbBackup
if err := app.backupRepo.ListByCond(map[string]any{}, &jobs); err != nil {
jobs, err := app.backupRepo.SelectByCond(map[string]any{})
if err != nil {
return err
}
for _, job := range jobs {
if ctx.Err() != nil {
return nil
}
var histories []*entity.DbBackupHistory
historyCond := map[string]any{
"db_backup_id": job.Id,
}
if err := app.backupHistoryRepo.SelectByCond(historyCond, &histories); err != nil {
return err
}
histories, _ := app.backupHistoryRepo.SelectByCond(historyCond)
expiringTime := time.Now().Add(-math.MaxInt64)
if job.MaxSaveDays > 0 {
expiringTime = time.Now().Add(-time.Hour * 24 * time.Duration(job.MaxSaveDays+1))
@@ -160,8 +157,8 @@ func (app *DbBackupApp) Enable(ctx context.Context, jobId uint64) error {
defer app.mutex.Unlock()
repo := app.backupRepo
job := &entity.DbBackup{}
if err := repo.GetById(job, jobId); err != nil {
job, err := repo.GetById(jobId)
if err != nil {
return err
}
if job.IsEnabled() {
@@ -183,8 +180,8 @@ func (app *DbBackupApp) Disable(ctx context.Context, jobId uint64) error {
defer app.mutex.Unlock()
repo := app.backupRepo
job := &entity.DbBackup{}
if err := repo.GetById(job, jobId); err != nil {
job, err := repo.GetById(jobId)
if err != nil {
return err
}
if !job.IsEnabled() {
@@ -202,8 +199,8 @@ func (app *DbBackupApp) StartNow(ctx context.Context, jobId uint64) error {
app.mutex.Lock()
defer app.mutex.Unlock()
job := &entity.DbBackup{}
if err := app.backupRepo.GetById(job, jobId); err != nil {
job, err := app.backupRepo.GetById(jobId)
if err != nil {
return err
}
if !job.IsEnabled() {
@@ -267,8 +264,8 @@ func (app *DbBackupApp) DeleteHistory(ctx context.Context, historyId uint64) (re
if !ok {
return errRestoringBackupHistory
}
job := &entity.DbBackupHistory{}
if err := app.backupHistoryRepo.GetById(job, historyId); err != nil {
job, err := app.backupHistoryRepo.GetById(historyId)
if err != nil {
return err
}
conn, err := app.dbApp.GetDbConnByInstanceId(job.DbInstanceId)

View File

@@ -75,8 +75,8 @@ func (app *DbBinlogApp) fetchBinlog(ctx context.Context) error {
}
func (app *DbBinlogApp) pruneBinlog(ctx context.Context) error {
var jobs []*entity.DbBinlog
if err := app.binlogRepo.SelectByCond(map[string]any{}, &jobs); err != nil {
jobs, err := app.binlogRepo.SelectByCond(map[string]any{})
if err != nil {
logx.Error("DbBinlogApp: 获取 BINLOG 同步任务失败: ", err.Error())
return err
}

View File

@@ -78,7 +78,7 @@ func (app *dataSyncAppImpl) Save(ctx context.Context, taskEntity *entity.DataSyn
return err
}
task, err := app.GetById(new(entity.DataSyncTask), taskEntity.Id)
task, err := app.GetById(taskEntity.Id)
if err != nil {
return err
}
@@ -112,7 +112,7 @@ func (app *dataSyncAppImpl) AddCronJob(ctx context.Context, taskEntity *entity.D
}
func (app *dataSyncAppImpl) RemoveCronJobById(taskId uint64) {
task, err := app.GetById(new(entity.DataSyncTask), taskId)
task, err := app.GetById(taskId)
if err == nil {
scheduler.RemoveByKey(task.TaskKey)
}
@@ -127,7 +127,7 @@ func (app *dataSyncAppImpl) changeRunningState(id uint64, state int8) {
func (app *dataSyncAppImpl) RunCronJob(ctx context.Context, id uint64) error {
// 查询最新的任务信息
task, err := app.GetById(new(entity.DataSyncTask), id)
task, err := app.GetById(id)
if err != nil {
return errorx.NewBiz("任务不存在")
}
@@ -369,7 +369,7 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
}
// 运行过程中,判断状态是否为已关闭,是则结束运行,否则继续运行
taskParam, _ := app.GetById(new(entity.DataSyncTask), task.Id)
taskParam, _ := app.GetById(task.Id)
if taskParam.RunningState == entity.DataSyncTaskRunStateStop {
return errorx.NewBiz("该任务已被手动终止")
}

View File

@@ -4,11 +4,13 @@ import (
"context"
"errors"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/application/dto"
"mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/biz"
@@ -21,12 +23,6 @@ import (
"gorm.io/gorm"
)
type SaveDbInstanceParam struct {
DbInstance *entity.DbInstance
AuthCerts []*tagentity.ResourceAuthCert
TagCodePaths []string
}
type Instance interface {
base.App[*entity.DbInstance]
@@ -35,7 +31,7 @@ type Instance interface {
TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error
SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) (uint64, error)
SaveDbInstance(ctx context.Context, instance *dto.SaveDbInstance) (uint64, error)
// Delete 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -91,7 +87,7 @@ func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance, authCert
return nil
}
func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) (uint64, error) {
func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.SaveDbInstance) (uint64, error) {
instanceEntity := instance.DbInstance
// 默认tcp连接
instanceEntity.Network = instanceEntity.GetNetwork()
@@ -128,7 +124,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
AuthCerts: authCerts,
})
}, func(ctx context.Context) error {
return app.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
return app.tagApp.SaveResourceTag(ctx, &tagdto.SaveResourceTag{
ResourceTag: app.genDbInstanceResourceTag(instanceEntity, authCerts),
ParentTagCodePaths: tagCodePaths,
})
@@ -142,7 +138,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
}
} else {
// 根据host等未查到旧数据则需要根据id重新获取因为后续需要使用到code
oldInstance, err = app.GetById(new(entity.DbInstance), instanceEntity.Id)
oldInstance, err = app.GetById(instanceEntity.Id)
if err != nil {
return 0, errorx.NewBiz("该数据库实例不存在")
}
@@ -162,7 +158,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
return err
}
}
return app.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
return app.tagApp.SaveResourceTag(ctx, &tagdto.SaveResourceTag{
ResourceTag: app.genDbInstanceResourceTag(instanceEntity, authCerts),
ParentTagCodePaths: tagCodePaths,
})
@@ -170,7 +166,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
}
func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error {
instance, err := app.GetById(new(entity.DbInstance), instanceId, "name")
instance, err := app.GetById(instanceId, "name")
if err != nil {
return errorx.NewBiz("获取数据库实例错误数据库实例ID为: %d", instance.Id)
}
@@ -201,18 +197,9 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
biz.ErrIsNil(err, "删除数据库实例失败: %v", err)
}
db := &entity.Db{
dbs, _ := app.dbApp.ListByCond(&entity.Db{
InstanceId: instanceId,
}
err = app.dbApp.GetByCond(db)
switch {
case err == nil:
biz.ErrNotNil(err, "不能删除数据库实例【%s】请先删除关联的数据库资源。", instance.Name)
case errors.Is(err, gorm.ErrRecordNotFound):
break
default:
biz.ErrIsNil(err, "删除数据库实例失败: %v", err)
}
})
return app.Tx(ctx, func(ctx context.Context) error {
return app.DeleteById(ctx, instanceId)
@@ -223,10 +210,18 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
})
}, func(ctx context.Context) error {
return app.tagApp.DeleteTagByParam(ctx, &tagapp.DelResourceTagParam{
return app.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
})
}, func(ctx context.Context) error {
// 删除所有库配置
for _, db := range dbs {
if err := app.dbApp.Delete(ctx, db.Id); err != nil {
return err
}
}
return nil
})
}
@@ -276,25 +271,25 @@ func (app *instanceAppImpl) toDbInfoByAc(instance *entity.DbInstance, ac *tagent
return di
}
func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCerts []*tagentity.ResourceAuthCert) *tagapp.ResourceTag {
authCertTags := collx.ArrayMap[*tagentity.ResourceAuthCert, *tagapp.ResourceTag](authCerts, func(val *tagentity.ResourceAuthCert) *tagapp.ResourceTag {
return &tagapp.ResourceTag{
func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCerts []*tagentity.ResourceAuthCert) *tagdto.ResourceTag {
authCertTags := collx.ArrayMap[*tagentity.ResourceAuthCert, *tagdto.ResourceTag](authCerts, func(val *tagentity.ResourceAuthCert) *tagdto.ResourceTag {
return &tagdto.ResourceTag{
Code: val.Name,
Name: val.Username,
Type: tagentity.TagTypeDbAuthCert,
}
})
var dbs []*entity.Db
if err := m.dbApp.ListByCond(&entity.Db{
dbs, err := m.dbApp.ListByCond(&entity.Db{
InstanceId: me.Id,
}, &dbs); err != nil {
})
if err != nil {
logx.Errorf("获取实例关联的数据库失败: %v", err)
}
authCertName2DbTags := make(map[string][]*tagapp.ResourceTag)
authCertName2DbTags := make(map[string][]*tagdto.ResourceTag)
for _, db := range dbs {
authCertName2DbTags[db.AuthCertName] = append(authCertName2DbTags[db.AuthCertName], &tagapp.ResourceTag{
authCertName2DbTags[db.AuthCertName] = append(authCertName2DbTags[db.AuthCertName], &tagdto.ResourceTag{
Code: db.Code,
Name: db.Name,
Type: tagentity.TagTypeDbName,
@@ -306,7 +301,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
ac.Children = authCertName2DbTags[ac.Code]
}
return &tagapp.ResourceTag{
return &tagdto.ResourceTag{
Code: me.Code,
Type: tagentity.TagTypeDb,
Name: me.Name,

View File

@@ -78,8 +78,8 @@ func (app *DbRestoreApp) Enable(ctx context.Context, jobId uint64) error {
defer app.mutex.Unlock()
repo := app.restoreRepo
job := &entity.DbRestore{}
if err := repo.GetById(job, jobId); err != nil {
job, err := repo.GetById(jobId)
if err != nil {
return err
}
if job.IsEnabled() {
@@ -101,8 +101,8 @@ func (app *DbRestoreApp) Disable(ctx context.Context, jobId uint64) error {
defer app.mutex.Unlock()
repo := app.restoreRepo
job := &entity.DbRestore{}
if err := repo.GetById(job, jobId); err != nil {
job, err := repo.GetById(jobId)
if err != nil {
return err
}
if !job.IsEnabled() {

View File

@@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"golang.org/x/sync/singleflight"
"gorm.io/gorm"
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
@@ -14,6 +12,9 @@ import (
"strconv"
"sync"
"time"
"golang.org/x/sync/singleflight"
"gorm.io/gorm"
)
const (
@@ -173,8 +174,8 @@ func (s *dbScheduler) restore(ctx context.Context, dbProgram dbi.DbProgram, rest
return err
}
} else {
backupHistory := &entity.DbBackupHistory{}
if err := s.backupHistoryRepo.GetById(backupHistory, restore.DbBackupHistoryId); err != nil {
backupHistory, err := s.backupHistoryRepo.GetById(restore.DbBackupHistoryId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("备份历史已删除")
}

View File

@@ -8,6 +8,7 @@ import (
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
flowapp "mayfly-go/internal/flow/application"
flowdto "mayfly-go/internal/flow/application/dto"
flowentity "mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
@@ -56,7 +57,7 @@ type DbSqlExec interface {
Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error)
// 根据条件删除sql执行记录
DeleteBy(ctx context.Context, condition *entity.DbSqlExec)
DeleteBy(ctx context.Context, condition *entity.DbSqlExec) error
// 分页获取
GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
@@ -67,6 +68,7 @@ type dbSqlExecAppImpl struct {
dbSqlExecRepo repository.DbSqlExec `inject:"DbSqlExecRepo"`
flowProcinstApp flowapp.Procinst `inject:"ProcinstApp"`
flowProcdefApp flowapp.Procdef `inject:"ProcdefApp"`
}
func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq) *entity.DbSqlExec {
@@ -196,8 +198,8 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *fl
return d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
}
func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSqlExec) {
d.dbSqlExecRepo.DeleteByCond(ctx, condition)
func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSqlExec) error {
return d.dbSqlExecRepo.DeleteByCond(ctx, condition)
}
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
@@ -348,11 +350,11 @@ func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, insert *sqlparser.Inser
func (d *dbSqlExecAppImpl) doExec(ctx context.Context, execSqlReq *DbSqlExecReq, dbSqlExecRecord *entity.DbSqlExec) (*DbSqlExecRes, error) {
dbConn := execSqlReq.DbConn
flowProcdefKey := dbConn.Info.FlowProcdefKey
if flowProcdefKey != "" {
if flowProcdefId := d.flowProcdefApp.GetProcdefIdByCodePath(ctx, dbConn.Info.CodePath...); flowProcdefId != 0 {
bizKey := stringx.Rand(24)
// 如果该库关联了审批流程,则启动流程实例即可
_, err := d.flowProcinstApp.StartProc(ctx, flowProcdefKey, &flowapp.StarProcParam{
_, err := d.flowProcinstApp.StartProc(ctx, flowProcdefId, &flowdto.StarProc{
BizType: DbSqlExecFlowBizType,
BizKey: bizKey,
Remark: dbSqlExecRecord.Remark,

View File

@@ -87,7 +87,7 @@ func (app *dbTransferAppImpl) CreateLog(ctx context.Context, taskId uint64) (uin
}
func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint64) {
task, err := app.GetById(new(entity.DbTransferTask), taskId)
task, err := app.GetById(taskId)
if err != nil {
logx.Errorf("创建DBMS-执行数据迁移日志失败:%v", err)
return
@@ -150,7 +150,7 @@ func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint
}
func (app *dbTransferAppImpl) Stop(ctx context.Context, taskId uint64) error {
task, err := app.GetById(new(entity.DbTransferTask), taskId)
task, err := app.GetById(taskId)
if err != nil {
return errorx.NewBiz("任务不存在")
}

View File

@@ -0,0 +1,23 @@
package dto
import (
"io"
"mayfly-go/internal/db/domain/entity"
tagentity "mayfly-go/internal/tag/domain/entity"
)
type SaveDbInstance struct {
DbInstance *entity.DbInstance
AuthCerts []*tagentity.ResourceAuthCert
TagCodePaths []string
}
type DumpDb struct {
DbId uint64
DbName string
Tables []string
DumpDDL bool // 是否dump ddl
DumpData bool // 是否dump data
Writer io.Writer
}

View File

@@ -1,13 +0,0 @@
package application
import "io"
type DumpDbReq struct {
DbId uint64
DbName string
Tables []string
DumpDDL bool // 是否dump ddl
DumpData bool // 是否dump data
Writer io.Writer
}

View File

@@ -47,8 +47,7 @@ type DbInfo struct {
Params string
Database string // 若有schema的库则为'database/scheam'格式
FlowProcdefKey string // 流程定义key
TagPath []string
CodePath []string
SshTunnelMachineId int
Meta Meta
@@ -56,7 +55,7 @@ type DbInfo struct {
// 获取记录日志的描述
func (d *DbInfo) GetLogDesc() string {
return fmt.Sprintf("DB[id=%d, tag=%s, name=%s, ip=%s:%d, database=%s]", d.Id, d.TagPath, d.Name, d.Host, d.Port, d.Database)
return fmt.Sprintf("DB[id=%d, tag=%s, name=%s, ip=%s:%d, database=%s]", d.Id, d.CodePath, d.Name, d.Host, d.Port, d.Database)
}
// 连接数据库

View File

@@ -52,7 +52,6 @@ func (md *MysqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri
}
func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
tableName := copy.TableName
// 生成新表名,为老表明+_copy_时间戳

View File

@@ -254,7 +254,7 @@ func (md *MysqlMetaData) GenerateTableDDL(columns []dbi.Column, tableInfo dbi.Ta
sqlArr := make([]string, 0)
if dropBeforeCreate {
sqlArr = append(sqlArr, fmt.Sprintf("DROP TABLE IF EXISTS %s;", meta.QuoteIdentifier(tableInfo.TableName)))
sqlArr = append(sqlArr, fmt.Sprintf("DROP TABLE IF EXISTS %s", meta.QuoteIdentifier(tableInfo.TableName)))
}
// 组装建表语句

View File

@@ -13,5 +13,4 @@ type Db struct {
Remark string `json:"remark"`
InstanceId uint64
AuthCertName string `json:"authCertName"`
FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
}

View File

@@ -14,6 +14,4 @@ type DbBackup interface {
// GetPageList 分页获取数据库任务列表
GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
ListByCond(cond any, listModels any, cols ...string) error
}

View File

@@ -22,7 +22,7 @@ func NewDbBackupRepo() repository.DbBackup {
func (d *dbBackupRepoImpl) GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error) {
var dbNamesWithBackup []string
err := global.Db.Model(d.GetModel()).
err := global.Db.Model(d.NewModel()).
Where("db_instance_id = ?", instanceId).
Where("repeated = ?", true).
Scopes(gormx.UndeleteScope).
@@ -41,7 +41,7 @@ func (d *dbBackupRepoImpl) GetDbNamesWithoutBackup(instanceId uint64, dbNames []
}
func (d *dbBackupRepoImpl) ListDbInstances(enabled bool, repeated bool, instanceIds *[]uint64) error {
return global.Db.Model(d.GetModel()).
return global.Db.Model(d.NewModel()).
Where("enabled = ?", enabled).
Where("repeated = ?", repeated).
Scopes(gormx.UndeleteScope).
@@ -51,7 +51,7 @@ func (d *dbBackupRepoImpl) ListDbInstances(enabled bool, repeated bool, instance
}
func (d *dbBackupRepoImpl) ListToDo(jobs any) error {
db := global.Db.Model(d.GetModel())
db := global.Db.Model(d.NewModel())
err := db.Where("enabled = ?", true).
Where(db.Where("repeated = ?", true).Or("last_status <> ?", entity.DbJobSuccess)).
Scopes(gormx.UndeleteScope).
@@ -70,7 +70,7 @@ func (d *dbBackupRepoImpl) GetPageList(condition *entity.DbBackupQuery, pagePara
Eq0("repeated", condition.Repeated).
In0("db_name", condition.InDbNames).
Like("db_name", condition.DbName)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}
// AddJob 添加数据库任务
@@ -91,7 +91,3 @@ func (d *dbBackupRepoImpl) UpdateEnabled(ctx context.Context, jobId uint64, enab
"enabled_desc": desc,
}, cond)
}
func (d *dbBackupRepoImpl) ListByCond(cond any, listModels any, cols ...string) error {
return d.dbJobBaseImpl.SelectByCond(model.NewModelCond(cond).Columns(cols...), listModels)
}

View File

@@ -29,11 +29,11 @@ func (repo *dbBackupHistoryRepoImpl) GetPageList(condition *entity.DbBackupHisto
In0("db_name", condition.InDbNames).
Eq("db_backup_id", condition.DbBackupId).
Eq("db_name", condition.DbName)
return repo.PageByCond(qd, pageParam, toEntity)
return repo.PageByCondToAny(qd, pageParam, toEntity)
}
func (repo *dbBackupHistoryRepoImpl) GetHistories(backupHistoryIds []uint64, toEntity any) error {
return global.Db.Model(repo.GetModel()).
return global.Db.Model(repo.NewModel()).
Where("id in ?", backupHistoryIds).
Where("deleting = false").
Scopes(gormx.UndeleteScope).
@@ -44,7 +44,7 @@ func (repo *dbBackupHistoryRepoImpl) GetHistories(backupHistoryIds []uint64, toE
func (repo *dbBackupHistoryRepoImpl) GetLatestHistoryForBinlog(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error) {
history := &entity.DbBackupHistory{}
db := global.Db
err := db.Model(repo.GetModel()).
err := db.Model(repo.NewModel()).
Where("db_instance_id = ?", instanceId).
Where("db_name = ?", dbName).
Where(db.Where("binlog_sequence < ?", bi.Sequence).
@@ -63,7 +63,7 @@ func (repo *dbBackupHistoryRepoImpl) GetLatestHistoryForBinlog(instanceId uint64
func (repo *dbBackupHistoryRepoImpl) GetEarliestHistoryForBinlog(instanceId uint64) (*entity.DbBackupHistory, bool, error) {
history := &entity.DbBackupHistory{}
db := global.Db.Model(repo.GetModel())
db := global.Db.Model(repo.NewModel())
err := db.Where("db_instance_id = ?", instanceId).
Where("binlog_sequence > 0").
Where("deleting = false").
@@ -81,7 +81,7 @@ func (repo *dbBackupHistoryRepoImpl) GetEarliestHistoryForBinlog(instanceId uint
}
func (repo *dbBackupHistoryRepoImpl) UpdateDeleting(deleting bool, backupHistoryId ...uint64) (bool, error) {
db := global.Db.Model(repo.GetModel()).
db := global.Db.Model(repo.NewModel()).
Where("id in ?", backupHistoryId).
Where("restoring = false").
Scopes(gormx.UndeleteScope).
@@ -96,7 +96,7 @@ func (repo *dbBackupHistoryRepoImpl) UpdateDeleting(deleting bool, backupHistory
}
func (repo *dbBackupHistoryRepoImpl) UpdateRestoring(restoring bool, backupHistoryId ...uint64) (bool, error) {
db := global.Db.Model(repo.GetModel()).
db := global.Db.Model(repo.NewModel()).
Where("id in ?", backupHistoryId).
Where("deleting = false").
Scopes(gormx.UndeleteScope).
@@ -111,7 +111,7 @@ func (repo *dbBackupHistoryRepoImpl) UpdateRestoring(restoring bool, backupHisto
}
func (repo *dbBackupHistoryRepoImpl) ZeroBinlogInfo(backupHistoryId uint64) error {
return global.Db.Model(repo.GetModel()).
return global.Db.Model(repo.NewModel()).
Where("id = ?", backupHistoryId).
Where("restoring = false").
Scopes(gormx.UndeleteScope).

View File

@@ -42,8 +42,8 @@ func (repo *dbBinlogHistoryRepoImpl) GetHistories(instanceId uint64, start, targ
Ge("sequence", start.Sequence).
Le("sequence", target.Sequence).
OrderByAsc("sequence")
var histories []*entity.DbBinlogHistory
if err := repo.SelectByCond(qc, &histories); err != nil {
histories, err := repo.SelectByCond(qc)
if err != nil {
return nil, err
}
if len(histories) == 0 {
@@ -121,7 +121,7 @@ func (repo *dbBinlogHistoryRepoImpl) InsertWithBinlogFiles(ctx context.Context,
}
func (repo *dbBinlogHistoryRepoImpl) GetHistoriesBeforeSequence(ctx context.Context, instanceId uint64, binlogSeq int64, histories *[]*entity.DbBinlogHistory) error {
return global.Db.Model(repo.GetModel()).
return global.Db.Model(repo.NewModel()).
Where("db_instance_id = ?", instanceId).
Where("sequence < ?", binlogSeq).
Scopes(gormx.UndeleteScope).

View File

@@ -20,7 +20,7 @@ func (d *dataSyncTaskRepoImpl) GetTaskList(condition *entity.DataSyncTaskQuery,
qd := model.NewCond().
Like("task_name", condition.Name).
Eq("status", condition.Status)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}
type dataSyncLogRepoImpl struct {
@@ -31,7 +31,7 @@ type dataSyncLogRepoImpl struct {
func (d *dataSyncLogRepoImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := model.NewCond().
Eq("task_id", condition.TaskId)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}
func newDataSyncLogRepo() repository.DataSyncLog {

View File

@@ -58,7 +58,7 @@ func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any
}
var res []string
err := db.Model(repo.GetModel()).Select("db_name").
err := db.Model(repo.NewModel()).Select("db_name").
Where("db_instance_id = ?", instanceId).
Where("db_name in ?", dbNames).
Where("repeated = true").

View File

@@ -22,7 +22,7 @@ func NewDbRestoreRepo() repository.DbRestore {
func (d *dbRestoreRepoImpl) GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error) {
var dbNamesWithRestore []string
err := global.Db.Model(d.GetModel()).
err := global.Db.Model(d.NewModel()).
Where("db_instance_id = ?", instanceId).
Where("repeated = ?", true).
Scopes(gormx.UndeleteScope).
@@ -42,7 +42,7 @@ func (d *dbRestoreRepoImpl) GetDbNamesWithoutRestore(instanceId uint64, dbNames
}
func (d *dbRestoreRepoImpl) ListToDo(jobs any) error {
db := global.Db.Model(d.GetModel())
db := global.Db.Model(d.NewModel())
err := db.Where("enabled = ?", true).
Where(db.Where("repeated = ?", true).Or("last_status <> ?", entity.DbJobSuccess)).
Scopes(gormx.UndeleteScope).
@@ -61,11 +61,11 @@ func (d *dbRestoreRepoImpl) GetPageList(condition *entity.DbRestoreQuery, pagePa
Eq0("repeated", condition.Repeated).
In0("db_name", condition.InDbNames).
Like("db_name", condition.DbName)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}
func (d *dbRestoreRepoImpl) GetEnabledRestores(toEntity any, backupHistoryId ...uint64) error {
return global.Db.Model(d.GetModel()).
return global.Db.Model(d.NewModel()).
Select("id", "db_backup_history_id", "last_status", "last_result", "last_time").
Where("db_backup_history_id in ?", backupHistoryId).
Where("enabled = true").

View File

@@ -21,6 +21,6 @@ func (d *dbRestoreHistoryRepoImpl) GetDbRestoreHistories(condition *entity.DbRes
qd := model.NewCond().
Eq("id", condition.Id).
Eq("db_backup_id", condition.DbRestoreId)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}

View File

@@ -25,5 +25,5 @@ func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pagePa
Eq("flow_biz_key", condition.FlowBizKey).
In("status", condition.Status).
RLike("db", condition.Db).OrderBy(orderBy...)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}

View File

@@ -20,5 +20,5 @@ func (d *dbTransferTaskRepoImpl) GetTaskList(condition *entity.DbTransferTaskQue
qd := model.NewCond()
//Like("task_name", condition.Name).
//Eq("status", condition.Status)
return d.PageByCond(qd, pageParam, toEntity)
return d.PageByCondToAny(qd, pageParam, toEntity)
}

Some files were not shown because too many files have changed in this diff Show More