mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4d949a64b | ||
|
|
3f6fb5afef | ||
|
|
68f553f4b0 | ||
|
|
7f2a49ba3c | ||
|
|
e56788af3e | ||
|
|
ebc89e056f | ||
|
|
d07cd74a8c | ||
|
|
6cc15ebeda | ||
|
|
2b712cd548 | ||
|
|
cda2963e1c | ||
|
|
bffa9c2676 |
@@ -1,5 +1,5 @@
|
||||
# 构建前端资源
|
||||
FROM m.daocloud.io/docker.io/node:18-bookworm-slim as fe-builder
|
||||
FROM m.daocloud.io/docker.io/node:18-bookworm-slim AS fe-builder
|
||||
|
||||
WORKDIR /mayfly
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN yarn config set registry 'https://registry.npmmirror.com' && \
|
||||
yarn build
|
||||
|
||||
# 构建后端资源
|
||||
FROM m.daocloud.io/docker.io/golang:1.23 as be-builder
|
||||
FROM m.daocloud.io/docker.io/golang:1.23 AS be-builder
|
||||
|
||||
ENV GOPROXY https://goproxy.cn
|
||||
WORKDIR /mayfly
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🌈mayfly-go
|
||||
# 🌈Dromara mayfly-go
|
||||
|
||||
<p align="center">
|
||||
<a href="./README_EN.md">English</a> |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🌈mayfly-go
|
||||
# 🌈Dromara mayfly-go
|
||||
|
||||
<p align="center">
|
||||
<a href="./README.md">中文介绍</a> |
|
||||
|
||||
@@ -56,6 +56,7 @@ function build() {
|
||||
if [ "${os}" == "windows" ];then
|
||||
execFileName="${execFileName}.exe"
|
||||
fi
|
||||
go mod tidy
|
||||
CGO_ENABLE=0 GOOS=${os} GOARCH=${arch} go build -ldflags=-w -o ${execFileName} main.go
|
||||
|
||||
if [ -d ${toFolder} ] ; then
|
||||
@@ -77,7 +78,7 @@ function build() {
|
||||
echo_green "Copy resources such as scripts [config.yml.example、mayfly-go.sql、mayfly-go.sqlite、readme.txt、startup.sh、shutdown.sh]"
|
||||
cp ${server_folder}/config.yml.example ${toFolder}
|
||||
cp ${server_folder}/readme.txt ${toFolder}
|
||||
cp ${server_folder}/readme_cn.txt ${toFolder}
|
||||
cp ${server_folder}/readme_en.txt ${toFolder}
|
||||
cp ${server_folder}/resources/script/startup.sh ${toFolder}
|
||||
cp ${server_folder}/resources/script/shutdown.sh ${toFolder}
|
||||
cp ${server_folder}/resources/script/sql/mayfly-go.sql ${toFolder}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"asciinema-player": "^3.8.1",
|
||||
"axios": "^1.6.2",
|
||||
"clipboard": "^2.0.11",
|
||||
@@ -19,26 +19,26 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.8.8",
|
||||
"element-plus": "^2.9.1",
|
||||
"js-base64": "^3.7.7",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"monaco-sql-languages": "^0.12.2",
|
||||
"monaco-themes": "^0.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.2.6",
|
||||
"qrcode.vue": "^3.5.1",
|
||||
"pinia": "^2.3.0",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.3",
|
||||
"sortablejs": "^1.15.6",
|
||||
"splitpanes": "^3.1.5",
|
||||
"sql-formatter": "^15.4.5",
|
||||
"trzsz": "^1.1.5",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.4",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-i18n": "^10.0.5",
|
||||
"vue-router": "^4.5.0",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0",
|
||||
"xterm-addon-search": "^0.13.0",
|
||||
@@ -52,16 +52,16 @@
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/compiler-sfc": "^3.5.13",
|
||||
"code-inspector-plugin": "^0.4.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"eslint-plugin-vue": "^9.31.0",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.81.0",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.11",
|
||||
"sass": "^1.82.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.3",
|
||||
"vue-eslint-parser": "^9.4.3"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -30,7 +30,12 @@ export function isTrue(condition: boolean, msg: string) {
|
||||
* @param msg 错误消息
|
||||
*/
|
||||
export function notBlank(obj: any, msg: string) {
|
||||
isTrue(obj, msg);
|
||||
if (obj == null || obj == undefined || obj == '') {
|
||||
throw new AssertError(msg);
|
||||
}
|
||||
if (Array.isArray(obj) && obj.length == 0) {
|
||||
throw new AssertError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,15 +19,22 @@ export const ResourceTypeEnum = {
|
||||
|
||||
// 标签关联的资源类型
|
||||
export const TagResourceTypeEnum = {
|
||||
AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
|
||||
PublicAuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
|
||||
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
|
||||
|
||||
Machine: ResourceTypeEnum.Machine,
|
||||
Db: ResourceTypeEnum.Db,
|
||||
DbInstance: ResourceTypeEnum.Db,
|
||||
Redis: ResourceTypeEnum.Redis,
|
||||
Mongo: ResourceTypeEnum.Mongo,
|
||||
AuthCert: EnumValue.of(5, '授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
|
||||
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
DbAuthCert: EnumValue.of(21, '数据库-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
DbName: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
|
||||
Db: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
|
||||
};
|
||||
|
||||
// 标签关联的资源类型路径
|
||||
export const TagResourceTypePath = {
|
||||
MachineAuthCert: `${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`,
|
||||
|
||||
DbInstanceAuthCert: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}`,
|
||||
Db: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}/${TagResourceTypeEnum.Db.value}`,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ const config = {
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.9.1',
|
||||
version: 'v1.9.2',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
getPublicKey: () => request.get('/common/public-key'),
|
||||
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
|
||||
getServerConf: () => request.get('/sys/configs/server'),
|
||||
oauth2LoginConfig: () => request.get('/auth/oauth2-config'),
|
||||
oauth2LoginConfig: () => request.get('/auth/oauth2/config'),
|
||||
changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
|
||||
captcha: () => request.get('/sys/captcha'),
|
||||
logout: () => request.post('/auth/accounts/logout'),
|
||||
|
||||
47
frontend/src/common/sysmsgs.ts
Normal file
47
frontend/src/common/sysmsgs.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
|
||||
import syssocket from './syssocket';
|
||||
import { h, reactive } from 'vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
|
||||
|
||||
export function initSysMsgs() {
|
||||
registerDbSqlExecProgress();
|
||||
}
|
||||
|
||||
const sqlExecNotifyMap: Map<string, any> = new Map();
|
||||
|
||||
function registerDbSqlExecProgress() {
|
||||
syssocket.registerMsgHandler('execSqlFileProgress', function (message: any) {
|
||||
const content = JSON.parse(message.msg);
|
||||
const id = content.id;
|
||||
let progress = sqlExecNotifyMap.get(id);
|
||||
if (content.terminated) {
|
||||
if (progress != undefined) {
|
||||
progress.notification?.close();
|
||||
sqlExecNotifyMap.delete(id);
|
||||
progress = undefined;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress == undefined) {
|
||||
progress = {
|
||||
props: reactive(buildProgressProps()),
|
||||
notification: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
progress.props.progress.title = content.title;
|
||||
progress.props.progress.executedStatements = content.executedStatements;
|
||||
if (!sqlExecNotifyMap.has(id)) {
|
||||
progress.notification = ElNotification({
|
||||
duration: 0,
|
||||
title: message.title,
|
||||
message: h(ProgressNotify, progress.props),
|
||||
type: syssocket.getMsgType(message.type),
|
||||
showClose: false,
|
||||
});
|
||||
sqlExecNotifyMap.set(id, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import Config from './config';
|
||||
import {ElNotification} from 'element-plus';
|
||||
import SocketBuilder from './SocketBuilder';
|
||||
import {getToken} from '@/common/utils/storage';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
|
||||
import {joinClientParams} from './request';
|
||||
import { joinClientParams } from './request';
|
||||
import { ElNotification } from 'element-plus';
|
||||
|
||||
class SysSocket {
|
||||
/**
|
||||
@@ -23,7 +23,6 @@ class SysSocket {
|
||||
0: 'error',
|
||||
1: 'success',
|
||||
2: 'info',
|
||||
22: 'info',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -57,21 +56,16 @@ class SysSocket {
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认通知处理
|
||||
const type = this.getMsgType(message.type);
|
||||
let msg = message.msg
|
||||
let duration = 0
|
||||
if (message.type == 22) {
|
||||
let obj = JSON.parse(msg);
|
||||
msg = `文件:${obj['title']} 执行成功: ${obj['executedStatements']} 条`
|
||||
duration = 2000
|
||||
}
|
||||
let msg = message.msg;
|
||||
let duration = 0;
|
||||
ElNotification({
|
||||
duration: duration,
|
||||
title: message.title,
|
||||
message: msg,
|
||||
type: type,
|
||||
});
|
||||
console.log(message)
|
||||
})
|
||||
.open((event: any) => console.log(event))
|
||||
.close(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-space :size="4">
|
||||
<span>{{ `${item?.label}` }}</span>
|
||||
<span>{{ `${$t(item?.label)}` }}</span>
|
||||
<el-tooltip v-if="item.tooltip" :content="item?.tooltip" placement="top">
|
||||
<SvgIcon name="QuestionFilled" />
|
||||
</el-tooltip>
|
||||
@@ -21,8 +21,8 @@
|
||||
</GridItem>
|
||||
<GridItem suffix>
|
||||
<div class="operation">
|
||||
<el-button type="primary" :icon="Search" @click="search" plain> 搜索 </el-button>
|
||||
<el-button :icon="Delete" @click="reset"> 重置 </el-button>
|
||||
<el-button type="primary" :icon="Search" @click="search" plain> {{ $t('common.search') }} </el-button>
|
||||
<el-button :icon="Delete" @click="reset"> {{ $t('common.reset') }} </el-button>
|
||||
<el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
|
||||
{{ collapsed ? '展开' : '合并' }}
|
||||
<el-icon class="el-icon--right">
|
||||
|
||||
@@ -196,6 +196,9 @@ watch(
|
||||
(newValue: any) => {
|
||||
if (!monacoEditorIns.hasTextFocus()) {
|
||||
state.languageMode = props.language;
|
||||
if (newValue == null) {
|
||||
newValue = '';
|
||||
}
|
||||
monacoEditorIns?.setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-descriptions border size="small" :title="`${progress.title}`">
|
||||
<el-descriptions border size="small" :title="`${props.progress.title}`">
|
||||
<el-descriptions-item label="时间">{{ state.elapsedTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="已处理">{{ progress.executedStatements }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
<!-- 按钮 -->
|
||||
<div class="search-buttons">
|
||||
<el-button class="terminal-search-button search-button-prev" type="primary" size="small" @click="searchKeywords(false)">
|
||||
{{ $t('components.terminal.previous') }}}}
|
||||
{{ $t('components.terminal.previous') }}
|
||||
</el-button>
|
||||
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="searchKeywords(true)">
|
||||
{{ $t('components.terminal.next') }}}}
|
||||
{{ $t('components.terminal.next') }}
|
||||
</el-button>
|
||||
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="closeSearch">
|
||||
{{ $t('components.terminal.close') }}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function useI18nPleaseSelect(labelI18nKey: string) {
|
||||
* @returns
|
||||
*/
|
||||
export async function useI18nDeleteConfirm(name: string = '') {
|
||||
return useI18nConfirm('common.deleteConfirm', { name });
|
||||
return useI18nConfirm('common.deleteConfirm2', { name });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,13 +45,15 @@ export default {
|
||||
previousStep: 'Previous Step',
|
||||
nextStep: 'Next Step',
|
||||
copy: 'Copy',
|
||||
search: 'Search',
|
||||
pleaseInput: 'Please enter {label}',
|
||||
pleaseSelect: 'Please select {label}',
|
||||
formValidationError: 'Please fill in the form information correctly',
|
||||
formValidationError: 'Please check the form',
|
||||
createTitle: 'Create {name}',
|
||||
editTitle: 'Edit {name}',
|
||||
detailTitle: '{name} Details',
|
||||
deleteConfirm: 'This operation will delete [{name}]. Do you want to continue?',
|
||||
deleteConfirm: 'Sure to delete?',
|
||||
deleteConfirm2: 'This operation will delete [{name}]. Do you want to continue?',
|
||||
saveSuccess: 'save successfully',
|
||||
deleteSuccess: 'delete successfully',
|
||||
operateSuccess: 'operate successfully',
|
||||
|
||||
@@ -73,6 +73,21 @@ export default {
|
||||
newTabRunSql: 'NewTab Run SQL',
|
||||
formatSql: 'Format SQL',
|
||||
|
||||
alias: 'Alias',
|
||||
tableName: 'Name',
|
||||
addColumn: 'Add Column',
|
||||
addDefaultColumn: 'Add Default Column',
|
||||
addIndex: 'Add Index',
|
||||
length: 'Length',
|
||||
numScale: 'Scale',
|
||||
defaultValue: 'Default Value',
|
||||
notNull: 'Not Null',
|
||||
primaryKey: 'Pri',
|
||||
autoIncrement: 'Auto Incr',
|
||||
unique: 'Unique',
|
||||
uniqueIndex: 'Unique Index',
|
||||
normalIndex: 'Normal Index',
|
||||
|
||||
execTime: 'execution time',
|
||||
oneClickCopy: 'One click copy',
|
||||
asc: 'Asc',
|
||||
@@ -199,7 +214,7 @@ export default {
|
||||
getDbNamesModeAssign: 'Specifying the db name',
|
||||
|
||||
ignore: 'Ignore',
|
||||
replate: 'Replate',
|
||||
replace: 'Replate',
|
||||
|
||||
running: 'Running',
|
||||
waitRun: 'Wait Run',
|
||||
|
||||
@@ -107,6 +107,9 @@ export default {
|
||||
cronjobRunState: 'Run State',
|
||||
execResRecordType: 'Result record type',
|
||||
cronExpression: 'Cron Expression',
|
||||
noRecord: 'Not recorded',
|
||||
onErrorRecord: 'Record on error',
|
||||
record: 'Record',
|
||||
|
||||
// file
|
||||
upload: 'Upload',
|
||||
|
||||
@@ -19,7 +19,6 @@ export default {
|
||||
newTabOpen: 'New tab opens',
|
||||
redisSelectErr: 'Select redis first',
|
||||
flushDbTips: 'Make sure to clear all keys of the [{db}] library?',
|
||||
keyNotEmpty: 'The Key cannot be empty',
|
||||
|
||||
// info
|
||||
redisInfoTitle: 'Redis server information',
|
||||
|
||||
@@ -45,13 +45,15 @@ export default {
|
||||
previousStep: '上一步',
|
||||
nextStep: '下一步',
|
||||
copy: '复制',
|
||||
search: '搜索',
|
||||
pleaseInput: '请输入{label}',
|
||||
pleaseSelect: '请选择{label}',
|
||||
formValidationError: '请正确填写表单信息',
|
||||
formValidationError: '信息填写有误,请检查',
|
||||
createTitle: '创建{name}',
|
||||
editTitle: '编辑{name}',
|
||||
detailTitle: '{name}详情',
|
||||
deleteConfirm: '此操作将删除【{name}】, 是否继续?',
|
||||
deleteConfirm: '确定删除?',
|
||||
deleteConfirm2: '此操作将删除【{name}】, 是否继续?',
|
||||
saveSuccess: '保存成功',
|
||||
deleteSuccess: '删除成功',
|
||||
operateSuccess: '操作成功',
|
||||
|
||||
@@ -72,6 +72,21 @@ export default {
|
||||
newTabRunSql: '新标签执行SQL',
|
||||
formatSql: '格式化SQL',
|
||||
|
||||
alias: '别名',
|
||||
tableName: '表名',
|
||||
addColumn: '添加列',
|
||||
addDefaultColumn: '添加默认列',
|
||||
addIndex: '添加索引',
|
||||
length: '长度',
|
||||
numScale: '精度',
|
||||
defaultValue: '默认值',
|
||||
notNull: '非空',
|
||||
primaryKey: '主键',
|
||||
autoIncrement: '自增',
|
||||
unique: '唯一',
|
||||
uniqueIndex: '唯一索引',
|
||||
normalIndex: '普通索引',
|
||||
|
||||
execTime: '执行时间',
|
||||
oneClickCopy: '一键复制',
|
||||
asc: '升序',
|
||||
@@ -195,7 +210,7 @@ export default {
|
||||
getDbNamesModeAssign: '指定库名',
|
||||
|
||||
ignore: '忽略',
|
||||
replate: '替换',
|
||||
replace: '替换',
|
||||
|
||||
running: '运行中',
|
||||
waitRun: '待运行',
|
||||
|
||||
@@ -108,6 +108,9 @@ export default {
|
||||
cronjobRunState: '运行状态',
|
||||
execResRecordType: '结果记录类型',
|
||||
cronExpression: 'cron表达式',
|
||||
noRecord: '不记录',
|
||||
onErrorRecord: '错误时记录',
|
||||
record: '记录',
|
||||
|
||||
// file
|
||||
upload: '上传',
|
||||
|
||||
@@ -17,7 +17,6 @@ export default {
|
||||
newTabOpen: '新tab打开',
|
||||
redisSelectErr: '请先选择redis',
|
||||
flushDbTips: '确定清空[{db}]库的所有key?',
|
||||
keyNotEmpty: 'Key不能为空',
|
||||
|
||||
// info
|
||||
redisInfoTitle: 'Redis服务器信息',
|
||||
|
||||
@@ -18,11 +18,13 @@ import '@/theme/index.scss';
|
||||
import '@/assets/font/font.css';
|
||||
import '@/assets/iconfont/iconfont.js';
|
||||
import { getThemeConfig } from './common/utils/storage';
|
||||
import { initSysMsgs } from './common/sysmsgs';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
registElSvgIcon(app);
|
||||
directive(app);
|
||||
initSysMsgs();
|
||||
|
||||
app.use(pinia).use(router).use(i18n).use(ElementPlus, { size: getThemeConfig()?.globalComponentSize }).mount('#app');
|
||||
|
||||
|
||||
@@ -12,30 +12,25 @@
|
||||
|
||||
/* Form 表单
|
||||
------------------------------- */
|
||||
.el-form {
|
||||
// .el-form {
|
||||
|
||||
// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
|
||||
.el-form-item:last-of-type {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
// // 修复行内表单最后一个 el-form-item 位置下移问题
|
||||
// &.el-form--inline {
|
||||
// .el-form-item--large.el-form-item:last-of-type {
|
||||
// margin-bottom: 22px !important;
|
||||
// }
|
||||
|
||||
// 修复行内表单最后一个 el-form-item 位置下移问题
|
||||
&.el-form--inline {
|
||||
.el-form-item--large.el-form-item:last-of-type {
|
||||
margin-bottom: 22px !important;
|
||||
}
|
||||
// .el-form-item--default.el-form-item:last-of-type,
|
||||
// .el-form-item--small.el-form-item:last-of-type {
|
||||
// margin-bottom: 18px !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
.el-form-item--default.el-form-item:last-of-type,
|
||||
.el-form-item--small.el-form-item:last-of-type {
|
||||
margin-bottom: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
|
||||
.el-form-item .el-form-item__label .el-icon {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
// .el-form-item .el-form-item__label .el-icon {
|
||||
// margin-right: 0px;
|
||||
// }
|
||||
// }
|
||||
|
||||
/* Alert 警告
|
||||
------------------------------- */
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('tag.relateTag')">
|
||||
<tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
|
||||
<tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypePath.Db, TagResourceTypeEnum.Redis.value]" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">{{ $t('flow.approvalNode') }}</el-divider>
|
||||
@@ -94,7 +94,7 @@ 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';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
|
||||
import { useI18nFormValidate, useI18nPleaseInput, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -95,7 +95,7 @@ const parseBizForm = async (bizFormStr: string) => {
|
||||
const dbRes = await dbApi.dbs.request({ id: bizForm.dbId });
|
||||
state.db = dbRes.list?.[0];
|
||||
|
||||
tagApi.listByQuery.request({ type: TagResourceTypeEnum.DbName.value, codes: state.db.code }).then((res) => {
|
||||
tagApi.listByQuery.request({ type: TagResourceTypeEnum.Db.value, codes: state.db.code }).then((res) => {
|
||||
state.db.codePaths = res.map((item: any) => item.codePath);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ watch(
|
||||
);
|
||||
|
||||
const changeResourceCode = async (db: any) => {
|
||||
emit('changeResourceCode', TagResourceTypeEnum.DbName.value, db.code);
|
||||
emit('changeResourceCode', TagResourceTypeEnum.Db.value, db.code);
|
||||
};
|
||||
|
||||
const validateBizForm = async () => {
|
||||
|
||||
@@ -122,7 +122,12 @@
|
||||
<template #header>
|
||||
<el-row justify="center">
|
||||
<div class="resource-num pointer-icon" @click="toPage('db')">
|
||||
<SvgIcon class="mb5 mr5" :size="28" :name="TagResourceTypeEnum.Db.extra.icon" :color="TagResourceTypeEnum.Db.extra.iconColor" />
|
||||
<SvgIcon
|
||||
class="mb5 mr5"
|
||||
:size="28"
|
||||
:name="TagResourceTypeEnum.DbInstance.extra.icon"
|
||||
:color="TagResourceTypeEnum.DbInstance.extra.iconColor"
|
||||
/>
|
||||
<span class="">{{ state.db.num }}</span>
|
||||
</div>
|
||||
</el-row>
|
||||
@@ -388,7 +393,7 @@ const handleAvatarSuccess = (response: any, uploadFile: any) => {
|
||||
// 初始化数字滚动
|
||||
const initData = async () => {
|
||||
resourceOpLogApi.getAccountResourceOpLogs
|
||||
.request({ resourceType: TagResourceTypeEnum.MachineAuthCert.value, pageSize: state.defaultLogSize })
|
||||
.request({ resourceType: TagResourceTypeEnum.Machine.value, pageSize: state.defaultLogSize })
|
||||
.then(async (res: any) => {
|
||||
const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
|
||||
state.machine.tagInfos = tagInfos;
|
||||
@@ -396,7 +401,7 @@ const initData = async () => {
|
||||
});
|
||||
|
||||
resourceOpLogApi.getAccountResourceOpLogs
|
||||
.request({ resourceType: TagResourceTypeEnum.DbName.value, pageSize: state.defaultLogSize })
|
||||
.request({ resourceType: TagResourceTypeEnum.DbInstance.value, pageSize: state.defaultLogSize })
|
||||
.then(async (res: any) => {
|
||||
const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
|
||||
state.db.tagInfos = tagInfos;
|
||||
|
||||
@@ -38,7 +38,11 @@
|
||||
:value="TagResourceTypeEnum.Machine.value"
|
||||
/>
|
||||
|
||||
<el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
|
||||
<el-option
|
||||
:key="TagResourceTypeEnum.DbInstance.value"
|
||||
:label="TagResourceTypeEnum.DbInstance.label"
|
||||
:value="TagResourceTypeEnum.DbInstance.value"
|
||||
/>
|
||||
<el-option
|
||||
:key="TagResourceTypeEnum.Redis.value"
|
||||
:label="$t(TagResourceTypeEnum.Redis.label)"
|
||||
@@ -154,7 +158,7 @@ const DefaultForm = {
|
||||
username: '',
|
||||
ciphertextType: AuthCertCiphertextTypeEnum.Password.value,
|
||||
type: AuthCertTypeEnum.Private.value,
|
||||
resourceType: TagResourceTypeEnum.AuthCert.value,
|
||||
resourceType: TagResourceTypeEnum.PublicAuthCert.value,
|
||||
resourceCode: '',
|
||||
ciphertext: '',
|
||||
extra: {} as any,
|
||||
|
||||
@@ -57,7 +57,7 @@ import { isPrefixSubsequence } from '@/common/utils/string';
|
||||
|
||||
const props = defineProps({
|
||||
resourceType: {
|
||||
type: [Number],
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
defaultExpandedKeys: {
|
||||
|
||||
@@ -58,7 +58,7 @@ const props = defineProps({
|
||||
default: 'calc(100vh - 330px)',
|
||||
},
|
||||
tagType: {
|
||||
type: [Number, Array<Number>],
|
||||
type: [Number, Array<Number>, String, Array<String>],
|
||||
default: TagResourceTypeEnum.Tag.value,
|
||||
},
|
||||
nodeKey: {
|
||||
|
||||
@@ -43,7 +43,7 @@ import { tagApi } from '../tag/api';
|
||||
|
||||
const props = defineProps({
|
||||
resourceType: {
|
||||
type: [Number],
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
tagPathNodeType: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {OptionsApi, SearchItem} from '@/components/SearchForm';
|
||||
import {ContextmenuItem} from '@/components/contextmenu';
|
||||
import {TagResourceTypeEnum} from '@/common/commonEnum';
|
||||
import {tagApi} from '../tag/api';
|
||||
import { OptionsApi, SearchItem } from '@/components/SearchForm';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { tagApi } from '../tag/api';
|
||||
|
||||
export class TagTreeNode {
|
||||
/**
|
||||
@@ -160,9 +160,9 @@ export class NodeType {
|
||||
* @param resourceType 资源类型
|
||||
* @returns
|
||||
*/
|
||||
export function getTagPathSearchItem(resourceType: number) {
|
||||
export function getTagPathSearchItem(resourceType: any) {
|
||||
return SearchItem.select('tagPath', 'common.tag').withOptionsApi(
|
||||
OptionsApi.new(tagApi.getResourceTagPaths, {resourceType}).withConvertFn((res: any) => {
|
||||
OptionsApi.new(tagApi.getResourceTagPaths, { resourceType }).withConvertFn((res: any) => {
|
||||
return res.map((x: any) => {
|
||||
return {
|
||||
label: x,
|
||||
@@ -180,7 +180,7 @@ export function getTagPathSearchItem(resourceType: number) {
|
||||
*/
|
||||
export function getTagTypeCodeByPath(codePath: string) {
|
||||
const result: any = {};
|
||||
if (!codePath) return result
|
||||
if (!codePath) return result;
|
||||
const parts = codePath.split('/'); // 切分字符串并保留数字和对应的值部分
|
||||
|
||||
for (let part of parts) {
|
||||
@@ -208,7 +208,7 @@ export function getTagTypeCodeByPath(codePath: string) {
|
||||
* @returns
|
||||
*/
|
||||
export async function getAllTagInfoByCodePaths(codePaths: string[]) {
|
||||
if (!codePaths) return
|
||||
if (!codePaths) return;
|
||||
const allTypeAndCode: any = {};
|
||||
|
||||
for (let codePath of codePaths) {
|
||||
@@ -222,7 +222,7 @@ export async function getAllTagInfoByCodePaths(codePaths: string[]) {
|
||||
if (type == TagResourceTypeEnum.Tag.value) {
|
||||
continue;
|
||||
}
|
||||
const tagInfo = await tagApi.listByQuery.request({type: type, codes: allTypeAndCode[type]});
|
||||
const tagInfo = await tagApi.listByQuery.request({ type: type, codes: allTypeAndCode[type] });
|
||||
allTypeAndCode[type] = tagInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ const getAuthCerts = async () => {
|
||||
const inst: any = props.instance;
|
||||
const res = await resourceAuthCertApi.listByQuery.request({
|
||||
resourceCode: inst.code,
|
||||
resourceType: TagResourceTypeEnum.Db.value,
|
||||
resourceType: TagResourceTypeEnum.DbInstance.value,
|
||||
pageSize: 100,
|
||||
});
|
||||
state.authCerts = res.list || [];
|
||||
|
||||
@@ -66,7 +66,7 @@ const searchItems = [
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('db', 'db.db'),
|
||||
TableColumn.new('table', 'db.type'),
|
||||
TableColumn.new('table', 'db.table'),
|
||||
TableColumn.new('type', 'db.stmtType').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
|
||||
TableColumn.new('creator', 'db.execUser'),
|
||||
TableColumn.new('sql', 'SQL').canBeautify(),
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
v-model:selection-data="state.selectionData"
|
||||
:columns="columns"
|
||||
@page-num-change="
|
||||
(args) => {
|
||||
(args: any) => {
|
||||
state.query.pageNum = args.pageNum;
|
||||
search();
|
||||
}
|
||||
"
|
||||
@page-size-change="
|
||||
(args) => {
|
||||
(args: any) => {
|
||||
state.query.pageSize = args.pageNum;
|
||||
search();
|
||||
}
|
||||
@@ -85,14 +85,14 @@ import { dbApi } from '@/views/ops/db/api';
|
||||
import { getDbDialect } from '@/views/ops/db/dialect';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import TerminalLog from '@/components/terminal/TerminalLog.vue';
|
||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
|
||||
import { getClientId } from '@/common/utils/storage';
|
||||
import FileInfo from '@/components/file/FileInfo.vue';
|
||||
import { DbTransferFileStatusEnum } from './enums';
|
||||
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nFormValidate, useI18nPleaseSelect, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nFormValidate, useI18nOperateSuccessMsg, useI18nPleaseSelect } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -179,14 +179,13 @@ const state = reactive({
|
||||
},
|
||||
btnOk: async function () {
|
||||
await useI18nFormValidate(runFormRef);
|
||||
console.log(state.runDialog.runForm);
|
||||
if (state.runDialog.runForm.targetDbType !== state.runDialog.runForm.dbType) {
|
||||
ElMessage.warning(t('db.targetDbTypeSelectError', { dbType: state.runDialog.runForm.dbType }));
|
||||
return false;
|
||||
}
|
||||
state.runDialog.runForm.clientId = getClientId();
|
||||
await dbApi.dbTransferFileRun.request(state.runDialog.runForm);
|
||||
useI18nSaveSuccessMsg();
|
||||
useI18nOperateSuccessMsg();
|
||||
state.runDialog.cancel();
|
||||
await search();
|
||||
},
|
||||
@@ -228,9 +227,7 @@ const openLog = function (data: any) {
|
||||
|
||||
// 运行sql,弹出选择需要运行的库,默认运行当前数据库,需要保证数据库类型与sql文件一致
|
||||
const openRun = function (data: any) {
|
||||
console.log(data);
|
||||
state.runDialog.runForm = { id: data.id, dbType: data.fileDbType } as any;
|
||||
console.log(state.runDialog.runForm);
|
||||
state.runDialog.visible = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<ResourceAuthCertTableEdit
|
||||
v-model="form.authCerts"
|
||||
:resource-code="form.code"
|
||||
:resource-type="TagResourceTypeEnum.Db.value"
|
||||
:resource-type="TagResourceTypeEnum.DbInstance.value"
|
||||
:test-conn-btn-loading="testConnBtnLoading"
|
||||
@test-conn="testConn"
|
||||
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
|
||||
@@ -128,6 +128,7 @@ import { AuthCertCiphertextTypeEnum } from '../tag/enums';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import { useI18nFormValidate, useI18nPleaseInput, useI18nPleaseSelect, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notBlankI18n } from '@/common/assert';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -258,6 +259,7 @@ const testConn = async (authCert: any) => {
|
||||
const btnOk = async () => {
|
||||
await useI18nFormValidate(dbForm);
|
||||
state.submitForm = await getReqForm();
|
||||
notBlankI18n(state.submitForm.authCerts, 'db.acName');
|
||||
await saveInstanceExec();
|
||||
useI18nSaveSuccessMsg();
|
||||
state.form.id = saveInstanceRes as any;
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">{{ $t('common.create') }}</el-button>
|
||||
<el-button v-auth="perms.delInstance" :disabled="selectionData.length < 1" @click="deleteInstance()" type="danger" icon="delete">{{
|
||||
$t('common.delete')
|
||||
}}</el-button>
|
||||
<el-button v-auth="perms.delInstance" :disabled="selectionData.length < 1" @click="deleteInstance()" type="danger" icon="delete">
|
||||
{{ $t('common.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
@@ -76,7 +76,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { dbApi } from './api';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
@@ -88,7 +87,7 @@ import { SearchItem } from '@/components/SearchForm';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -110,10 +109,7 @@ const perms = {
|
||||
saveDb: 'db:save',
|
||||
};
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.Db.value),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'), getTagPathSearchItem(TagResourceTypePath.Db)];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('tags[0].tagPath', 'tag.relateTag').isSlot('tagPath').setAddWidth(20),
|
||||
@@ -209,7 +205,7 @@ const editInstance = async (data: any) => {
|
||||
|
||||
const deleteInstance = async () => {
|
||||
try {
|
||||
useI18nDeleteConfirm(state.selectionData.map((x: any) => x.name).join('、'));
|
||||
await useI18nDeleteConfirm(state.selectionData.map((x: any) => x.name).join('、'));
|
||||
await dbApi.deleteInstance.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
|
||||
useI18nDeleteSuccessMsg();
|
||||
search();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
:resource-type="TagResourceTypeEnum.DbName.value"
|
||||
:resource-type="TagResourceTypePath.Db"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
ref="tagTreeRef"
|
||||
>
|
||||
@@ -246,7 +246,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
import { getDbDialect, schemaDbTypes } from './dialect/index';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
import { useEventListener, useStorage } from '@vueuse/core';
|
||||
import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
|
||||
@@ -597,7 +597,7 @@ const autoOpenDb = (codePath: string) => {
|
||||
const typeAndCodes: any = getTagTypeCodeByPath(codePath);
|
||||
const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
|
||||
|
||||
const dbCode = typeAndCodes[TagResourceTypeEnum.DbName.value][0];
|
||||
const dbCode = typeAndCodes[TagResourceTypeEnum.Db.value][0];
|
||||
state.defaultExpendKey = [tagPath, dbCode];
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -136,11 +136,11 @@
|
||||
|
||||
<el-tab-pane :label="$t('db.fieldMap')" :name="fieldTab" :disabled="!baseFieldCompleted">
|
||||
<el-form-item prop="fieldMap" :label="$t('db.fieldMap')" required>
|
||||
<el-table :data="form.fieldMap" :max-height="400" size="small">
|
||||
<el-table :data="form.fieldMap" :max-height="650" size="small">
|
||||
<el-table-column prop="src" :label="$t('db.srcField')" :width="200" />
|
||||
<el-table-column prop="target" :label="$t('db.targetField')">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.target">
|
||||
<el-select v-model="scope.row.target" allow-create filterable>
|
||||
<el-option
|
||||
v-for="item in state.targetColumnList"
|
||||
:key="item.columnName"
|
||||
@@ -155,15 +155,15 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('db.sqlPreview')" :name="sqlPreviewTab" :disabled="!baseFieldCompleted">
|
||||
<el-form-item prop="fieldMap" :label="$t('db.selectSql')">
|
||||
<el-input type="textarea" v-model="state.previewDataSql" readonly :input-style="{ height: '170px' }" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="fieldMap" :label="$t('db.insertSql')">
|
||||
<el-input type="textarea" v-model="state.previewInsertSql" readonly :input-style="{ height: '170px' }" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="isReplace" v-if="compatibleDuplicateStrategy(form.targetDbType!)" :label="$t('db.keyDuplicateStrategy')">
|
||||
<EnumSelect :enums="DbDataSyncDuplicateStrategyEnum" v-model="form.duplicateStrategy" @change="handleDuplicateStrategy" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="fieldMap" :label="$t('db.selectSql')">
|
||||
<el-input type="textarea" v-model="state.previewDataSql" readonly :input-style="{ height: '300px' }" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="fieldMap" :label="$t('db.insertSql')">
|
||||
<el-input type="textarea" v-model="state.previewInsertSql" readonly :input-style="{ height: '300px' }" />
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
@@ -502,11 +502,13 @@ const handleGetSrcFields = async () => {
|
||||
sql,
|
||||
});
|
||||
|
||||
if (!res.columns) {
|
||||
if (res.length && !res[0].columns) {
|
||||
ElMessage.warning(t('db.notColumnSql'));
|
||||
return;
|
||||
}
|
||||
|
||||
let data = res[0];
|
||||
|
||||
let filedMap: any = {};
|
||||
if (state.form.fieldMap && state.form.fieldMap.length > 0) {
|
||||
state.form.fieldMap.forEach((a: any) => {
|
||||
@@ -514,11 +516,11 @@ const handleGetSrcFields = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
state.srcTableFields = res.columns.map((a: any) => a.name);
|
||||
state.srcTableFields = data.columns.map((a: any) => a.name);
|
||||
|
||||
state.form.fieldMap = res.columns.map((a: any) => ({ src: a.name, target: filedMap[a.name] || '' }));
|
||||
state.form.fieldMap = data.columns.map((a: any) => ({ src: a.name, target: filedMap[a.name] || '' }));
|
||||
|
||||
state.previewRes = res;
|
||||
state.previewRes = data;
|
||||
};
|
||||
|
||||
const handleGetTargetFields = async () => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-bind="$attrs"
|
||||
v-model="selectNode"
|
||||
@change="changeNode"
|
||||
:resource-type="TagResourceTypeEnum.Db.value"
|
||||
:resource-type="TagResourceTypePath.Db"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
>
|
||||
<template #iconPrefix>
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
|
||||
import { dbApi } from '@/views/ops/db/api';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
</el-row>
|
||||
<db-table-data
|
||||
v-if="!dt.errorMsg"
|
||||
:ref="(el) => (dt.dbTableRef = el)"
|
||||
:ref="(el: any) => (dt.dbTableRef = el)"
|
||||
:db-id="dbId"
|
||||
:db="dbName"
|
||||
:data="dt.data"
|
||||
@@ -128,12 +128,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, nextTick, onMounted, reactive, ref, toRefs, unref } from 'vue';
|
||||
import { nextTick, onMounted, reactive, ref, toRefs, unref } from 'vue';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import { notBlank } from '@/common/assert';
|
||||
import { format as sqlFormatter } from 'sql-formatter';
|
||||
import config from '@/common/config';
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { editor } from 'monaco-editor';
|
||||
@@ -144,9 +144,6 @@ import { dbApi } from '../../api';
|
||||
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
|
||||
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
|
||||
import syssocket from '@/common/syssocket';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -303,17 +300,17 @@ const onRunSql = async (newTab = false) => {
|
||||
|
||||
const sqls = splitSql(sql);
|
||||
|
||||
// 简单截取前十个字符
|
||||
const sqlPrefix = sql.slice(0, 10).toLowerCase();
|
||||
const nonQuery =
|
||||
sqlPrefix.startsWith('update') ||
|
||||
sqlPrefix.startsWith('insert') ||
|
||||
sqlPrefix.startsWith('delete') ||
|
||||
sqlPrefix.startsWith('alert') ||
|
||||
sqlPrefix.startsWith('drop') ||
|
||||
sqlPrefix.startsWith('create');
|
||||
|
||||
if (sqls.length == 1) {
|
||||
const oneSql = sqls[0];
|
||||
// 简单截取前十个字符
|
||||
const sqlPrefix = oneSql.slice(0, 10).toLowerCase();
|
||||
const nonQuery =
|
||||
sqlPrefix.startsWith('update') ||
|
||||
sqlPrefix.startsWith('insert') ||
|
||||
sqlPrefix.startsWith('delete') ||
|
||||
sqlPrefix.startsWith('alter') ||
|
||||
sqlPrefix.startsWith('drop') ||
|
||||
sqlPrefix.startsWith('create');
|
||||
let execRemark;
|
||||
if (nonQuery) {
|
||||
const res: any = await ElMessageBox.prompt(t('db.enterExecRemarkTips'), 'Tip', {
|
||||
@@ -323,7 +320,7 @@ const onRunSql = async (newTab = false) => {
|
||||
});
|
||||
execRemark = res.value;
|
||||
}
|
||||
runSql(sql, execRemark, newTab);
|
||||
runSql(oneSql, execRemark, newTab);
|
||||
} else {
|
||||
let isFirst = true;
|
||||
for (let s of sqls) {
|
||||
@@ -593,44 +590,8 @@ const replaceSelection = (str: string, selection: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* sql文件执行进度通知缓存
|
||||
*/
|
||||
const sqlExecNotifyMap: Map<string, any> = new Map();
|
||||
const beforeUpload = (file: File) => {
|
||||
ElMessage.success(t('db.scriptFileUploadRunning', { filename: file.name }));
|
||||
syssocket.registerMsgHandler('execSqlFileProgress', function (message: any) {
|
||||
const content = JSON.parse(message.msg);
|
||||
const id = content.id;
|
||||
let progress = sqlExecNotifyMap.get(id);
|
||||
if (content.terminated) {
|
||||
if (progress != undefined) {
|
||||
progress.notification?.close();
|
||||
sqlExecNotifyMap.delete(id);
|
||||
progress = undefined;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress == undefined) {
|
||||
progress = {
|
||||
props: reactive(buildProgressProps()),
|
||||
notification: undefined,
|
||||
};
|
||||
}
|
||||
progress.props.progress.title = content.title;
|
||||
progress.props.progress.executedStatements = content.executedStatements;
|
||||
if (!sqlExecNotifyMap.has(id)) {
|
||||
progress.notification = ElNotification({
|
||||
duration: 0,
|
||||
title: message.title,
|
||||
message: h(ProgressNotify, progress.props),
|
||||
type: syssocket.getMsgType(message.type),
|
||||
showClose: false,
|
||||
});
|
||||
sqlExecNotifyMap.set(id, progress);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 执行sql成功
|
||||
|
||||
@@ -1,36 +1,54 @@
|
||||
<template>
|
||||
<div class="string-input-container w100" v-if="dataType == DataType.String || dataType == DataType.Number">
|
||||
<el-input
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:ref="
|
||||
(el: any) => {
|
||||
nextTick(() => {
|
||||
focus && el?.focus();
|
||||
});
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
@blur="handleBlur"
|
||||
:class="`w100 mb4 ${showEditorIcon ? 'string-input-container-show-icon' : ''}`"
|
||||
size="small"
|
||||
v-model="itemValue"
|
||||
:placeholder="placeholder"
|
||||
:placeholder="placeholder ?? $t('common.pleaseInput')"
|
||||
/>
|
||||
<SvgIcon v-if="showEditorIcon" @mousedown="openEditor" class="string-input-container-icon" name="FullScreen" :size="10" />
|
||||
</div>
|
||||
|
||||
<el-date-picker
|
||||
v-else-if="dataType == DataType.Date"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:ref="
|
||||
(el: any) => {
|
||||
nextTick(() => {
|
||||
focus && el?.focus();
|
||||
});
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
@change="emit('blur')"
|
||||
@change="handleBlur"
|
||||
@blur="handleBlur"
|
||||
class="edit-time-picker mb4"
|
||||
popper-class="edit-time-picker-popper"
|
||||
size="small"
|
||||
v-model="itemValue"
|
||||
:clearable="false"
|
||||
type="Date"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="`date-${placeholder}`"
|
||||
:placeholder="`date-${placeholder ?? $t('common.pleaseSelect')}`"
|
||||
/>
|
||||
|
||||
<el-date-picker
|
||||
v-else-if="dataType == DataType.DateTime"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:ref="
|
||||
(el: any) => {
|
||||
nextTick(() => {
|
||||
focus && el?.focus();
|
||||
});
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
@change="handleBlur"
|
||||
@blur="handleBlur"
|
||||
@@ -41,12 +59,18 @@
|
||||
:clearable="false"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="`datetime-${placeholder}`"
|
||||
:placeholder="`datetime-${placeholder ?? $t('common.pleaseSelect')}`"
|
||||
/>
|
||||
|
||||
<el-time-picker
|
||||
v-else-if="dataType == DataType.Time"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:ref="
|
||||
(el: any) => {
|
||||
nextTick(() => {
|
||||
focus && el?.focus();
|
||||
});
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
@change="handleBlur"
|
||||
@blur="handleBlur"
|
||||
@@ -56,12 +80,12 @@
|
||||
v-model="itemValue"
|
||||
:clearable="false"
|
||||
value-format="HH:mm:ss"
|
||||
:placeholder="`time-${placeholder}`"
|
||||
:placeholder="`time-${placeholder ?? $t('common.pleaseSelect')}`"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, Ref } from 'vue';
|
||||
import { computed, nextTick, ref, Ref } from 'vue';
|
||||
import { ElInput, ElMessage } from 'element-plus';
|
||||
import { DataType } from '../../dialect/index';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch, Ref } from 'vue';
|
||||
import { ElInput, ElMessage } from 'element-plus';
|
||||
import { copyToClipboard } from '@/common/utils/string';
|
||||
import { DbInst, DbThemeConfig } from '@/views/ops/db/db';
|
||||
@@ -164,7 +164,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { exportCsv, exportFile } from '@/common/utils/export';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { useIntervalFn, useStorage } from '@vueuse/core';
|
||||
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
|
||||
import { ColumnTypeSubscript, DataType, DbDialect, getDbDialect } from '../../dialect/index';
|
||||
import ColumnFormItem from './ColumnFormItem.vue';
|
||||
import DbTableDataForm from './DbTableDataForm.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -239,9 +239,11 @@ const cmHeaderFixed = new ContextmenuItem('fixed', 'db.fixed')
|
||||
})
|
||||
.withHideFunc((data: any) => data.fixed);
|
||||
|
||||
const cmHeaderCancenFixed = new ContextmenuItem('cancelFixed', 'db.cancelFiexd')
|
||||
const cmHeaderCancelFixed = new ContextmenuItem('cancelFixed', 'db.cancelFiexd')
|
||||
.withIcon('Minus')
|
||||
.withOnClick((data: any) => (data.fixed = false))
|
||||
.withOnClick((data: any) => {
|
||||
data.fixed = false;
|
||||
})
|
||||
.withHideFunc((data: any) => !data.fixed);
|
||||
|
||||
/** 表数据 contextmenu items **/
|
||||
@@ -253,7 +255,7 @@ const cmDataCopyCell = new ContextmenuItem('copyValue', 'common.copy')
|
||||
})
|
||||
.withHideFunc(() => {
|
||||
// 选中多条则隐藏该复制按钮
|
||||
return selectionRowsMap.size > 1;
|
||||
return selectionRowsMap.value.size > 1;
|
||||
});
|
||||
|
||||
const cmDataDel = new ContextmenuItem('deleteData', 'common.delete')
|
||||
@@ -307,7 +309,7 @@ class UpdatedRow {
|
||||
/**
|
||||
* 修改到的列信息, columnName -> tablecelldata
|
||||
*/
|
||||
columnsMap: Map<string, TableCellData> = new Map();
|
||||
columnsMap = new Map<string, TableCellData>();
|
||||
}
|
||||
|
||||
class TableCellData {
|
||||
@@ -319,16 +321,16 @@ class TableCellData {
|
||||
|
||||
let dbDialect: DbDialect = null as any;
|
||||
|
||||
let nowSortColumn = null as any;
|
||||
let nowSortColumn = ref(null) as any;
|
||||
|
||||
// 当前正在更新的单元格
|
||||
let nowUpdateCell: NowUpdateCell = null as any;
|
||||
let nowUpdateCell: Ref<NowUpdateCell> = ref(null) as any;
|
||||
|
||||
// 选中的数据, key->rowIndex value->primaryKeyValue
|
||||
const selectionRowsMap: Map<number, any> = new Map();
|
||||
const selectionRowsMap = ref(new Map<number, any>());
|
||||
|
||||
// 更新单元格 key-> rowIndex value -> 更新行
|
||||
const cellUpdateMap: Map<number, UpdatedRow> = new Map();
|
||||
const cellUpdateMap = ref(new Map<number, UpdatedRow>());
|
||||
|
||||
// 数据加载时间计时器
|
||||
const { pause, resume } = useIntervalFn(() => {
|
||||
@@ -452,24 +454,11 @@ onBeforeUnmount(() => {
|
||||
endLoading();
|
||||
});
|
||||
|
||||
const formatDataValues = (datas: any) => {
|
||||
// mysql数据暂不做处理
|
||||
if (compatibleMysql(getNowDbInst().type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let data of datas) {
|
||||
for (let column of props.columns!) {
|
||||
data[column.columnName] = getFormatTimeValue(dbDialect.getDataType(column.dataType), data[column.columnName]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setTableData = (datas: any) => {
|
||||
tableRef.value?.scrollTo({ scrollLeft: 0, scrollTop: 0 });
|
||||
selectionRowsMap.clear();
|
||||
cellUpdateMap.clear();
|
||||
formatDataValues(datas);
|
||||
selectionRowsMap.value.clear();
|
||||
cellUpdateMap.value.clear();
|
||||
// formatDataValues(datas);
|
||||
state.datas = datas;
|
||||
setTableColumns(props.columns);
|
||||
};
|
||||
@@ -481,7 +470,6 @@ const setTableColumns = (columns: any) => {
|
||||
x.dataType = dbDialect.getDataType(x.columnType);
|
||||
x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
|
||||
x.remark = `${x.columnType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
|
||||
|
||||
return {
|
||||
...x,
|
||||
key: columnName,
|
||||
@@ -520,7 +508,7 @@ const cancelLoading = async () => {
|
||||
* @param colIndex ci
|
||||
*/
|
||||
const canEdit = (rowIndex: number, colIndex: number) => {
|
||||
return state.table && nowUpdateCell?.rowIndex == rowIndex && nowUpdateCell?.colIndex == colIndex;
|
||||
return state.table && nowUpdateCell.value?.rowIndex == rowIndex && nowUpdateCell.value?.colIndex == colIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -529,7 +517,7 @@ const canEdit = (rowIndex: number, colIndex: number) => {
|
||||
* @param columnName cn
|
||||
*/
|
||||
const isUpdated = (rowIndex: number, columnName: string) => {
|
||||
return cellUpdateMap.get(rowIndex)?.columnsMap.get(columnName);
|
||||
return cellUpdateMap.value.get(rowIndex)?.columnsMap.get(columnName);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -537,7 +525,7 @@ const isUpdated = (rowIndex: number, columnName: string) => {
|
||||
* @param rowIndex
|
||||
*/
|
||||
const isSelection = (rowIndex: number): boolean => {
|
||||
return selectionRowsMap.get(rowIndex);
|
||||
return selectionRowsMap.value.get(rowIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -549,16 +537,14 @@ const isSelection = (rowIndex: number): boolean => {
|
||||
const selectionRow = (rowIndex: number, rowData: any, isMultiple = false) => {
|
||||
if (isMultiple) {
|
||||
// 如果重复点击,则取消改选中数据
|
||||
if (selectionRowsMap.get(rowIndex)) {
|
||||
selectionRowsMap.delete(rowIndex);
|
||||
triggerRefresh();
|
||||
if (selectionRowsMap.value.get(rowIndex)) {
|
||||
selectionRowsMap.value.delete(rowIndex);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
selectionRowsMap.clear();
|
||||
selectionRowsMap.value.clear();
|
||||
}
|
||||
selectionRowsMap.set(rowIndex, rowData);
|
||||
triggerRefresh();
|
||||
selectionRowsMap.value.set(rowIndex, rowData);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -584,7 +570,7 @@ const headerContextmenuClick = (event: any, data: any) => {
|
||||
const { clientX, clientY } = event;
|
||||
state.contextmenu.dropdown.x = clientX;
|
||||
state.contextmenu.dropdown.y = clientY;
|
||||
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancenFixed];
|
||||
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancelFixed];
|
||||
contextmenuRef.value.openContextmenu(data);
|
||||
};
|
||||
|
||||
@@ -606,7 +592,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
|
||||
* 表排序字段变更
|
||||
*/
|
||||
const onTableSortChange = async (sort: any) => {
|
||||
nowSortColumn = sort;
|
||||
nowSortColumn.value = sort;
|
||||
cancelUpdateFields();
|
||||
emits('sortChange', sort);
|
||||
};
|
||||
@@ -615,7 +601,7 @@ const onTableSortChange = async (sort: any) => {
|
||||
* 执行删除数据事件
|
||||
*/
|
||||
const onDeleteData = async () => {
|
||||
const deleteDatas = Array.from(selectionRowsMap.values());
|
||||
const deleteDatas = Array.from(selectionRowsMap.value.values());
|
||||
const db = state.db;
|
||||
const dbInst = getNowDbInst();
|
||||
dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
|
||||
@@ -624,7 +610,7 @@ const onDeleteData = async () => {
|
||||
};
|
||||
|
||||
const onEditRowData = () => {
|
||||
const selectionDatas = Array.from(selectionRowsMap.values());
|
||||
const selectionDatas = Array.from(selectionRowsMap.value.values());
|
||||
if (selectionDatas.length > 1) {
|
||||
ElMessage.warning(t('db.onlySelectOneData'));
|
||||
return;
|
||||
@@ -636,14 +622,14 @@ const onEditRowData = () => {
|
||||
};
|
||||
|
||||
const onGenerateInsertSql = async () => {
|
||||
const selectionDatas = Array.from(selectionRowsMap.values());
|
||||
const selectionDatas = Array.from(selectionRowsMap.value.values());
|
||||
state.genTxtDialog.txt = await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas);
|
||||
state.genTxtDialog.title = 'SQL';
|
||||
state.genTxtDialog.visible = true;
|
||||
};
|
||||
|
||||
const onGenerateJson = async () => {
|
||||
const selectionDatas = Array.from(selectionRowsMap.values());
|
||||
const selectionDatas = Array.from(selectionRowsMap.value.values());
|
||||
// 按列字段重新排序对象key
|
||||
const jsonObj = [];
|
||||
for (let selectionData of selectionDatas) {
|
||||
@@ -685,12 +671,12 @@ const onExportSql = async () => {
|
||||
};
|
||||
|
||||
const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex = 0) => {
|
||||
if (!state.table) {
|
||||
// 不存在表,或者已经在编辑中,则不处理
|
||||
if (!state.table || nowUpdateCell.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerRefresh();
|
||||
nowUpdateCell = {
|
||||
nowUpdateCell.value = {
|
||||
rowIndex: rowIndex,
|
||||
colIndex: columnIndex,
|
||||
oldValue: rowData[column.dataKey],
|
||||
@@ -699,24 +685,23 @@ const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex =
|
||||
};
|
||||
|
||||
const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
if (!nowUpdateCell) {
|
||||
if (!nowUpdateCell.value) {
|
||||
return;
|
||||
}
|
||||
const oldValue = nowUpdateCell.oldValue;
|
||||
const oldValue = nowUpdateCell.value.oldValue;
|
||||
const newValue = rowData[column.dataKey];
|
||||
|
||||
// 未改变单元格值
|
||||
if (oldValue == newValue) {
|
||||
nowUpdateCell = null as any;
|
||||
triggerRefresh();
|
||||
nowUpdateCell.value = null as any;
|
||||
return;
|
||||
}
|
||||
|
||||
let updatedRow = cellUpdateMap.get(rowIndex);
|
||||
let updatedRow = cellUpdateMap.value.get(rowIndex);
|
||||
if (!updatedRow) {
|
||||
updatedRow = new UpdatedRow();
|
||||
updatedRow.rowData = rowData;
|
||||
cellUpdateMap.set(rowIndex, updatedRow);
|
||||
cellUpdateMap.value.set(rowIndex, updatedRow);
|
||||
}
|
||||
|
||||
const columnName = column.dataKey;
|
||||
@@ -724,7 +709,7 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
if (cellData) {
|
||||
// 多次修改情况,可能又修改回原值,则移除该修改单元格
|
||||
if (cellData.oldValue == newValue) {
|
||||
cellUpdateMap.delete(rowIndex);
|
||||
cellUpdateMap.value.delete(rowIndex);
|
||||
}
|
||||
} else {
|
||||
cellData = new TableCellData();
|
||||
@@ -732,21 +717,20 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
|
||||
updatedRow.columnsMap.set(columnName, cellData);
|
||||
}
|
||||
|
||||
nowUpdateCell = null as any;
|
||||
triggerRefresh();
|
||||
nowUpdateCell.value = null as any;
|
||||
changeUpdatedField();
|
||||
};
|
||||
|
||||
const submitUpdateFields = async () => {
|
||||
const dbInst = getNowDbInst();
|
||||
if (cellUpdateMap.size == 0) {
|
||||
if (cellUpdateMap.value.size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const db = state.db;
|
||||
let res = '';
|
||||
|
||||
for (let updateRow of cellUpdateMap.values()) {
|
||||
for (let updateRow of cellUpdateMap.value.values()) {
|
||||
const rowData = { ...updateRow.rowData };
|
||||
let updateColumnValue: any = {};
|
||||
|
||||
@@ -763,14 +747,13 @@ const submitUpdateFields = async () => {
|
||||
}
|
||||
|
||||
dbInst.promptExeSql(db, res, null, () => {
|
||||
triggerRefresh();
|
||||
cellUpdateMap.clear();
|
||||
cellUpdateMap.value.clear();
|
||||
changeUpdatedField();
|
||||
});
|
||||
};
|
||||
|
||||
const cancelUpdateFields = () => {
|
||||
const updateRows = cellUpdateMap.values();
|
||||
const updateRows = cellUpdateMap.value.values();
|
||||
// 恢复原值
|
||||
for (let updateRow of updateRows) {
|
||||
const rowData = updateRow.rowData;
|
||||
@@ -778,12 +761,12 @@ const cancelUpdateFields = () => {
|
||||
rowData[k] = v.oldValue;
|
||||
});
|
||||
}
|
||||
cellUpdateMap.clear();
|
||||
cellUpdateMap.value.clear();
|
||||
changeUpdatedField();
|
||||
};
|
||||
|
||||
const changeUpdatedField = () => {
|
||||
emits('changeUpdatedField', cellUpdateMap);
|
||||
emits('changeUpdatedField', cellUpdateMap.value);
|
||||
};
|
||||
|
||||
const rowClass = (row: any) => {
|
||||
@@ -793,44 +776,6 @@ const rowClass = (row: any) => {
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据数据库返回的时间字段类型,获取格式化后的时间值
|
||||
* @param dataType getDataType返回的数据类型
|
||||
* @param originValue 原始值
|
||||
* @return 格式化后的值
|
||||
*/
|
||||
const getFormatTimeValue = (dataType: DataType, originValue: string): string => {
|
||||
if (!originValue || dataType === DataType.Number || dataType === DataType.String) {
|
||||
return originValue;
|
||||
}
|
||||
|
||||
// 把Z去掉
|
||||
originValue = originValue.replace('Z', '');
|
||||
|
||||
switch (dataType) {
|
||||
case DataType.Time:
|
||||
return formatDate(originValue, 'HH:mm:ss');
|
||||
case DataType.Date:
|
||||
return formatDate(originValue, 'YYYY-MM-DD');
|
||||
case DataType.DateTime:
|
||||
return formatDate(originValue, 'YYYY-MM-DD HH:mm:ss');
|
||||
default:
|
||||
return originValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 触发响应式实时刷新,否则需要滑动或移动才能使样式实时生效
|
||||
*/
|
||||
const triggerRefresh = () => {
|
||||
// 改变columns等属性值,才能触发slot中的if条件等, 暂不知为啥
|
||||
if (state.columns[0].opTimes) {
|
||||
state.columns[0].opTimes = state.columns[0].opTimes + 1;
|
||||
} else {
|
||||
state.columns[0].opTimes = 1;
|
||||
}
|
||||
};
|
||||
|
||||
const scrollLeftValue = ref(0);
|
||||
const onTableScroll = (param: any) => {
|
||||
scrollLeftValue.value = param.scrollLeft;
|
||||
|
||||
@@ -592,7 +592,7 @@ const onSelectByCondition = async () => {
|
||||
*/
|
||||
const onTableSortChange = async (sort: any) => {
|
||||
const sortType = sort.order == 'desc' ? 'DESC' : 'ASC';
|
||||
state.orderBy = `ORDER BY ${sort.columnName} ${sortType}`;
|
||||
state.orderBy = `ORDER BY ${state.dbDialect.quoteIdentifier(sort.columnName)} ${sortType}`;
|
||||
await onRefresh();
|
||||
};
|
||||
|
||||
|
||||
@@ -7,25 +7,31 @@
|
||||
<el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="tableName" label="表名">
|
||||
<el-form-item prop="tableName" :label="$t('db.tableName')">
|
||||
<el-input style="width: 80%" v-model="tableData.tableName" size="small"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="tableComment" label="备注">
|
||||
<el-form-item prop="tableComment" :label="$t('db.comment')">
|
||||
<el-input style="width: 80%" v-model="tableData.tableComment" size="small"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="字段" name="1">
|
||||
<el-tab-pane :label="$t('db.column')" name="1">
|
||||
<el-table ref="tableRef" :data="tableData.fields.res" :max-height="tableData.height">
|
||||
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.fields.colNames" :key="item.prop" :width="item.width">
|
||||
<el-table-column
|
||||
:prop="item.prop"
|
||||
:label="$t(item.label)"
|
||||
v-for="item in tableData.fields.colNames"
|
||||
:key="item.prop"
|
||||
:width="item.width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name" />
|
||||
|
||||
<el-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
|
||||
<el-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type" allow-create>
|
||||
<el-option
|
||||
v-for="pgsqlType in getDbDialect(dbType!).getInfo().columnTypes"
|
||||
:key="pgsqlType.dataType"
|
||||
@@ -35,7 +41,7 @@
|
||||
<span v-if="pgsqlType.dataType === pgsqlType.udtName"
|
||||
>{{ pgsqlType.dataType }}{{ pgsqlType.desc && ':' + pgsqlType.desc }}</span
|
||||
>
|
||||
<span v-else>{{ pgsqlType.dataType }},别名:{{ pgsqlType.udtName }} {{ pgsqlType.desc }}</span>
|
||||
<span v-else>{{ pgsqlType.dataType }},{{ $t('db.alias') }}: {{ pgsqlType.udtName }} {{ pgsqlType.desc }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
@@ -58,22 +64,22 @@
|
||||
|
||||
<el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark" />
|
||||
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteRow(scope.$index)">
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" :title="$t('common.deleteConfirm')" @confirm="deleteRow(scope.$index)">
|
||||
<template #reference>
|
||||
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
|
||||
<el-link type="danger" plain size="small" :underline="false">{{ $t('common.delete') }}</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row style="margin-top: 20px">
|
||||
<el-button @click="addDefaultRows()" link type="warning" icon="plus">添加默认列</el-button>
|
||||
<el-button @click="addRow()" link type="primary" icon="plus">添加列</el-button>
|
||||
<el-button @click="addDefaultRows()" link type="warning" icon="plus">{{ $t('db.addDefaultColumn') }}</el-button>
|
||||
<el-button @click="addRow()" link type="primary" icon="plus">{{ $t('db.addColumn') }}</el-button>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="索引" name="2">
|
||||
<el-tab-pane :label="$t('db.index')" name="2">
|
||||
<el-table :data="tableData.indexs.res" :max-height="tableData.height">
|
||||
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.indexs.colNames" :key="item.prop">
|
||||
<el-table-column :prop="item.prop" :label="$t(item.label)" v-for="item in tableData.indexs.colNames" :key="item.prop">
|
||||
<template #default="scope">
|
||||
<el-input v-if="item.prop === 'indexName'" size="small" disabled v-model="scope.row.indexName"></el-input>
|
||||
|
||||
@@ -84,7 +90,6 @@
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
filterable
|
||||
placeholder="请选择字段"
|
||||
@change="indexChanges(scope.row)"
|
||||
style="width: 100%"
|
||||
>
|
||||
@@ -100,9 +105,9 @@
|
||||
|
||||
<el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
|
||||
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteIndex(scope.$index)">
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" :title="$t('common.deleteConfirm')" @confirm="deleteIndex(scope.$index)">
|
||||
<template #reference>
|
||||
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
|
||||
<el-link type="danger" plain size="small" :underline="false">{{ $t('common.delete') }}</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
@@ -110,14 +115,14 @@
|
||||
</el-table>
|
||||
|
||||
<el-row style="margin-top: 20px">
|
||||
<el-button @click="addIndex()" link type="primary" icon="plus">添加索引</el-button>
|
||||
<el-button @click="addIndex()" link type="primary" icon="plus">{{ $t('db.addIndex') }}</el-button>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="cancel()">取消</el-button>
|
||||
<el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button>
|
||||
<el-button @click="cancel()">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button :loading="btnloading" @click="submit()" type="primary">{{ $t('common.save') }}</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@@ -129,6 +134,9 @@ import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
||||
import { DbInst } from '../../db';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -177,52 +185,52 @@ const state = reactive({
|
||||
colNames: [
|
||||
{
|
||||
prop: 'name',
|
||||
label: '字段名称',
|
||||
label: 'db.columnName',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
prop: 'type',
|
||||
label: '字段类型',
|
||||
label: 'common.type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
prop: 'length',
|
||||
label: '长度',
|
||||
label: 'db.length',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
prop: 'numScale',
|
||||
label: '小数精度',
|
||||
label: 'db.numScale',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
prop: 'value',
|
||||
label: '默认值',
|
||||
label: 'db.defaultValue',
|
||||
width: 120,
|
||||
},
|
||||
|
||||
{
|
||||
prop: 'notNull',
|
||||
label: '非空',
|
||||
label: 'db.notNull',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
prop: 'pri',
|
||||
label: '主键',
|
||||
label: 'db.primaryKey',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
prop: 'auto_increment',
|
||||
label: '自增',
|
||||
label: 'db.autoIncrement',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
prop: 'remark',
|
||||
label: '备注',
|
||||
label: 'db.comment',
|
||||
},
|
||||
{
|
||||
prop: 'action',
|
||||
label: '操作',
|
||||
label: 'common.operation',
|
||||
width: 70,
|
||||
},
|
||||
] as ColName[],
|
||||
@@ -233,27 +241,27 @@ const state = reactive({
|
||||
colNames: [
|
||||
{
|
||||
prop: 'indexName',
|
||||
label: '索引名',
|
||||
label: 'common.name',
|
||||
},
|
||||
{
|
||||
prop: 'columnNames',
|
||||
label: '列名',
|
||||
label: 'db.columnName',
|
||||
},
|
||||
{
|
||||
prop: 'unique',
|
||||
label: '唯一',
|
||||
label: 'db.unique',
|
||||
},
|
||||
{
|
||||
prop: 'indexType',
|
||||
label: '类型',
|
||||
label: 'common.type',
|
||||
},
|
||||
{
|
||||
prop: 'indexComment',
|
||||
label: '备注',
|
||||
label: 'db.comment',
|
||||
},
|
||||
{
|
||||
prop: 'action',
|
||||
label: '操作',
|
||||
label: 'common.operation',
|
||||
},
|
||||
],
|
||||
columns: [{ name: '', remark: '' }],
|
||||
@@ -336,7 +344,7 @@ const deleteIndex = (index: any) => {
|
||||
const submit = async () => {
|
||||
let sql = genSql();
|
||||
if (!sql) {
|
||||
ElMessage.warning('没有更改');
|
||||
ElMessage.warning(t('db.noChange'));
|
||||
return;
|
||||
}
|
||||
SqlExecBox({
|
||||
@@ -472,10 +480,10 @@ const indexChanges = (row: any) => {
|
||||
}
|
||||
|
||||
let suffix = row.unique ? 'udx' : 'idx';
|
||||
let commentSuffix = row.unique ? '唯一索引' : '普通索引';
|
||||
let commentSuffix = row.unique ? t('db.uniqueIndex') : t('db.normalIndex');
|
||||
// 以表名为前缀
|
||||
row.indexName = `${tableData.value.tableName}_${name}_${suffix}`.replaceAll(' ', '');
|
||||
row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
|
||||
row.indexComment = `${tableData.value.tableName} ${t('db.table')} (${name.replaceAll('_', ',')})${commentSuffix}`;
|
||||
};
|
||||
|
||||
const disableEditIncr = () => {
|
||||
|
||||
@@ -123,7 +123,7 @@ export class DbInst {
|
||||
},
|
||||
kind: monaco.languages.CompletionItemKind.File,
|
||||
detail: tableComment,
|
||||
insertText: dbDialect.quoteIdentifier(tableName) + ' ',
|
||||
insertText: dbDialect.quoteIdentifier(tableName),
|
||||
range,
|
||||
sortText: 300 + index + '',
|
||||
});
|
||||
@@ -147,7 +147,7 @@ export class DbInst {
|
||||
},
|
||||
kind: monaco.languages.CompletionItemKind.Property,
|
||||
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
|
||||
insertText: dbDialect.quoteIdentifier(fieldName) + ' ', // create_time
|
||||
insertText: dbDialect.quoteIdentifier(fieldName), // create_time
|
||||
range,
|
||||
sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
|
||||
});
|
||||
@@ -261,7 +261,8 @@ export class DbInst {
|
||||
* @returns count sql
|
||||
*/
|
||||
getDefaultCountSql = (table: string, condition?: string) => {
|
||||
return `SELECT COUNT(*) count FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''}`;
|
||||
return `SELECT COUNT(*) count
|
||||
FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''}`;
|
||||
};
|
||||
|
||||
// 获取指定表的默认查询sql
|
||||
@@ -303,7 +304,8 @@ export class DbInst {
|
||||
colNames.push(this.wrapName(colName));
|
||||
values.push(dbDialect.wrapValue(column.dataType, data[colName]));
|
||||
}
|
||||
sqls.push(`INSERT INTO ${schema}${this.wrapName(table)} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
|
||||
sqls.push(`INSERT INTO ${schema}${this.wrapName(table)} (${colNames.join(', ')})
|
||||
VALUES (${values.join(', ')})`);
|
||||
}
|
||||
return sqls.join(';\n') + ';';
|
||||
}
|
||||
@@ -322,7 +324,8 @@ export class DbInst {
|
||||
schema = this.wrapName(dbArr[1]) + '.';
|
||||
}
|
||||
|
||||
let sql = `UPDATE ${schema}${this.wrapName(table)} SET `;
|
||||
let sql = `UPDATE ${schema}${this.wrapName(table)}
|
||||
SET `;
|
||||
// 主键列信息
|
||||
const primaryKey = await this.loadTableColumn(dbName, table);
|
||||
let primaryKeyType = primaryKey.dataType;
|
||||
@@ -350,7 +353,9 @@ export class DbInst {
|
||||
const primaryKey = await this.loadTableColumn(db, table);
|
||||
const primaryKeyColumnName = primaryKey.columnName;
|
||||
const ids = datas.map((d: any) => `${this.getDialect().wrapValue(primaryKey.dataType, d[primaryKeyColumnName])}`).join(',');
|
||||
return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
|
||||
return `DELETE
|
||||
FROM ${this.wrapName(table)}
|
||||
WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -459,7 +464,7 @@ export class DbInst {
|
||||
* @returns
|
||||
*/
|
||||
static isNumber(columnType: string) {
|
||||
return columnType && columnType.match(/int|double|float|number|decimal|byte|bit/gi);
|
||||
return columnType && columnType.match(/(int|uint|double|float|number|decimal|byte|bit)/gi);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -814,7 +819,7 @@ export function registerDbCompletionItemProvider(dbId: number, db: string, dbs:
|
||||
},
|
||||
kind: monaco.languages.CompletionItemKind.File,
|
||||
detail: tableComment,
|
||||
insertText: dbDialect.quoteIdentifier(tableName) + ' ',
|
||||
insertText: dbDialect.quoteIdentifier(tableName),
|
||||
range,
|
||||
sortText: 300 + index + '',
|
||||
});
|
||||
@@ -833,7 +838,17 @@ export function registerDbCompletionItemProvider(dbId: number, db: string, dbs:
|
||||
});
|
||||
}
|
||||
|
||||
function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string): { tableName: string; tableAlias: string; db: string } | undefined {
|
||||
function getTableName4SqlCtx(
|
||||
sql: string,
|
||||
alias: string = '',
|
||||
defaultDb: string
|
||||
):
|
||||
| {
|
||||
tableName: string;
|
||||
tableAlias: string;
|
||||
db: string;
|
||||
}
|
||||
| undefined {
|
||||
// 去除多余的换行、空格和制表符
|
||||
sql = sql.replace(/[\r\n\s\t]+/g, ' ');
|
||||
|
||||
|
||||
@@ -54,6 +54,21 @@ const DM_TYPE_LIST: sqlColumnType[] = [
|
||||
{ udtName: 'BLOB', dataType: 'BLOB', desc: '变长的二进制大对象', space: '', range: '100G-1' },
|
||||
{ udtName: 'CLOB', dataType: 'CLOB', desc: '同TEXT', space: '', range: '100G-1' },
|
||||
{ udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '100G-1' },
|
||||
|
||||
// 达梦系统特有的字段类型 从 ALL_TYPES 表中查询得到
|
||||
{udtName: 'SYSGEO.ST_CURVE', dataType: 'SYSGEO.ST_CURVE', desc: '表示一条曲线,可以是圆弧、抛物线等', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_LINESTRING', dataType: 'SYSGEO.ST_LINESTRING', desc: '表示一条或多条连续的线段', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_GEOMCOLLECTION', dataType: 'SYSGEO.ST_GEOMCOLLECTION', desc: '表示一个几何对象集合,可以包含多个不同类型的几何对象', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_GEOMETRY', dataType: 'SYSGEO.ST_GEOMETRY', desc: '通用几何对象类型,可以表示点、线、面等任何几何形状', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_MULTICURVE', dataType: 'SYSGEO.ST_MULTICURVE', desc: '表示多个曲线的集合', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_MULTILINESTRING', dataType: 'SYSGEO.ST_MULTILINESTRING', desc: '表示多个线串的集合', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_MULTIPOINT', dataType: 'SYSGEO.ST_MULTIPOINT', desc: '表示多个点的集合', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_MULTIPOLYGON', dataType: 'SYSGEO.ST_MULTIPOLYGON', desc: '表示多个多边形的集合', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_MULTISURFACE', dataType: 'SYSGEO.ST_MULTISURFACE', desc: '表示多个表面的集合', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_POINT', dataType: 'SYSGEO.ST_POINT', desc: '表示一个点', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_POLYGON', dataType: 'SYSGEO.ST_POLYGON', desc: '表示一个多边形', space: '', range: ''},
|
||||
{udtName: 'SYSGEO.ST_SURFACE', dataType: 'SYSGEO.ST_SURFACE', desc: '表示一个表面,通常是一个多边形', space: '', range: ''},
|
||||
|
||||
];
|
||||
|
||||
// 参考官方文档:https://eco.dameng.com/document/dm/zh-cn/pm/function.html
|
||||
|
||||
@@ -209,8 +209,7 @@ class PostgresqlDialect implements DbDialect {
|
||||
}
|
||||
|
||||
quoteIdentifier = (name: string) => {
|
||||
// 后端sql解析器暂不支持pgsql
|
||||
return name;
|
||||
return `"${name}"`;
|
||||
};
|
||||
|
||||
matchType(text: string, arr: string[]): boolean {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const DbSqlExecStatusEnum = {
|
||||
|
||||
export const DbDataSyncDuplicateStrategyEnum = {
|
||||
None: EnumValue.of(-1, 'db.none'),
|
||||
Ignore: EnumValue.of(1, 'db.ingore'),
|
||||
Ignore: EnumValue.of(1, 'db.ignore'),
|
||||
Replace: EnumValue.of(2, 'db.replace'),
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">{{ $t('common.other') }}</el-divider>
|
||||
<el-form-item prop="enableRecorder" :label="$t('machine.sshTunnel')">
|
||||
<el-form-item prop="enableRecorder" :label="$t('machine.terminalPlayback')">
|
||||
<el-checkbox v-model="form.enableRecorder" :true-value="1" :false-value="-1"></el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
@@ -71,13 +71,12 @@
|
||||
<template #status="{ data }">
|
||||
<el-switch
|
||||
v-auth:disabled="'machine:update'"
|
||||
:width="52"
|
||||
v-model="data.status"
|
||||
:active-value="1"
|
||||
:inactive-value="-1"
|
||||
inline-prompt
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
:active-text="$t('common.enable')"
|
||||
:inactive-text="$t('common.disable')"
|
||||
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
|
||||
@change="changeStatus(data)"
|
||||
></el-switch>
|
||||
@@ -270,7 +269,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { formatByteSize, formatDate } from '@/common/utils/format';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
||||
@@ -312,7 +311,7 @@ const perms = {
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('machine.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.MachineAuthCert.value),
|
||||
getTagPathSearchItem(TagResourceTypePath.MachineAuthCert),
|
||||
];
|
||||
|
||||
const columns = [
|
||||
@@ -320,7 +319,7 @@ const columns = [
|
||||
TableColumn.new('name', 'common.name'),
|
||||
TableColumn.new('ipPort', 'Ip:Port').isSlot().setAddWidth(50),
|
||||
TableColumn.new('authCerts[0].username', 'machine.acName').isSlot('authCert').setAddWidth(10),
|
||||
TableColumn.new('status', 'common.status').isSlot().setMinWidth(85),
|
||||
TableColumn.new('status', 'common.status').isSlot(),
|
||||
TableColumn.new('stat', 'machine.runningStat').isSlot().setAddWidth(55),
|
||||
TableColumn.new('fs', 'machine.fs').isSlot().setAddWidth(25),
|
||||
TableColumn.new('remark', 'common.remark'),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<tag-tree
|
||||
class="machine-terminal-tree"
|
||||
ref="tagTreeRef"
|
||||
:resource-type="TagResourceTypeEnum.MachineAuthCert.value"
|
||||
:resource-type="TagResourceTypePath.MachineAuthCert"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
>
|
||||
@@ -168,7 +168,7 @@ import { useRouter } from 'vue-router';
|
||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
@@ -378,7 +378,7 @@ const autoOpenTerminal = (codePath: string) => {
|
||||
const machineCode = typeAndCodes[TagResourceTypeEnum.Machine.value][0];
|
||||
state.defaultExpendKey = [tagPath, machineCode];
|
||||
|
||||
const authCertName = typeAndCodes[TagResourceTypeEnum.MachineAuthCert.value][0];
|
||||
const authCertName = typeAndCodes[TagResourceTypeEnum.PublicAuthCert.value][0];
|
||||
setTimeout(() => {
|
||||
// 置空
|
||||
autoOpenResourceStore.setMachineCodePath('');
|
||||
|
||||
@@ -39,7 +39,11 @@
|
||||
/></el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('machine.relateMachine')">
|
||||
<tag-tree-check height="200px" :tag-type="TagResourceTypeEnum.Machine.value" v-model="form.codePaths" />
|
||||
<tag-tree-check
|
||||
height="200px"
|
||||
:tag-type="`${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`"
|
||||
v-model="form.codePaths"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ export const CronJobStatusEnum = {
|
||||
|
||||
// 计划任务保存执行结果类型
|
||||
export const CronJobSaveExecResTypeEnum = {
|
||||
No: EnumValue.of(-1, '不记录').tagTypeDanger(),
|
||||
OnError: EnumValue.of(1, '错误时记录').tagTypeWarning(),
|
||||
Yes: EnumValue.of(2, '记录').tagTypeSuccess(),
|
||||
No: EnumValue.of(-1, 'machine.noRecord').tagTypeDanger(),
|
||||
OnError: EnumValue.of(1, 'machine.onErrorRecord').tagTypeWarning(),
|
||||
Yes: EnumValue.of(2, 'machine.record').tagTypeSuccess(),
|
||||
};
|
||||
|
||||
// 计划任务执行记录状态
|
||||
|
||||
@@ -185,22 +185,19 @@
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="mode" :label="$t('machine.attribute')" width="110"> </el-table-column>
|
||||
<el-table-column
|
||||
v-if="$props.protocol == MachineProtocolEnum.Ssh.value"
|
||||
prop="username"
|
||||
:label="$t('machine.user')"
|
||||
min-width="55"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
|
||||
<el-table-column v-if="$props.protocol == MachineProtocolEnum.Ssh.value" :label="$t('machine.user')" min-width="70" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ userMap.get(scope.row.uid)?.uname || scope.row.uid }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="$props.protocol == MachineProtocolEnum.Ssh.value"
|
||||
prop="groupname"
|
||||
:label="$t('machine.group')"
|
||||
min-width="55"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
|
||||
<el-table-column v-if="$props.protocol == MachineProtocolEnum.Ssh.value" :label="$t('machine.group')" min-width="70" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ groupMap.get(scope.row.gid)?.gname || scope.row.gid }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="modTime" :label="$t('machine.modificationTime')" width="160" sortable> </el-table-column>
|
||||
|
||||
<el-table-column :width="130">
|
||||
@@ -276,8 +273,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" :label="$t('common.type')">
|
||||
<el-radio-group v-model="createFileDialog.type">
|
||||
<el-radio value="d" label="d">{{ $t('machine.file') }}</el-radio>
|
||||
<el-radio value="-" label="-">{{ $t('machine.folder') }}</el-radio>
|
||||
<el-radio value="d" label="d">{{ $t('machine.folder') }}</el-radio>
|
||||
<el-radio value="-" label="-">{{ $t('machine.file') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -303,7 +300,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import { ElInput, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ElInput, ElMessage } from 'element-plus';
|
||||
import { machineApi } from '../api';
|
||||
|
||||
import { joinClientParams } from '@/common/request';
|
||||
@@ -334,8 +331,8 @@ const folderUploadRef: any = ref();
|
||||
|
||||
const folderType = 'd';
|
||||
|
||||
const userMap = new Map<number, any>();
|
||||
const groupMap = new Map<number, any>();
|
||||
const userMap = ref(new Map<number, any>());
|
||||
const groupMap = ref(new Map<number, any>());
|
||||
|
||||
// 路径分隔符
|
||||
const pathSep = '/';
|
||||
@@ -382,13 +379,13 @@ onMounted(async () => {
|
||||
if (props.protocol == MachineProtocolEnum.Ssh.value) {
|
||||
machineApi.users.request({ id: machineId }).then((res: any) => {
|
||||
for (let user of res) {
|
||||
userMap.set(user.uid, user);
|
||||
userMap.value.set(user.uid, user);
|
||||
}
|
||||
});
|
||||
|
||||
machineApi.groups.request({ id: machineId }).then((res: any) => {
|
||||
for (let group of res) {
|
||||
groupMap.set(group.gid, group);
|
||||
groupMap.value.set(group.gid, group);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -565,11 +562,6 @@ const lsFile = async (path: string) => {
|
||||
path,
|
||||
});
|
||||
for (const file of res) {
|
||||
if (props.protocol == MachineProtocolEnum.Ssh.value) {
|
||||
file.username = userMap.get(file.uid)?.uname || file.uid;
|
||||
file.groupname = groupMap.get(file.gid)?.gname || file.gid;
|
||||
}
|
||||
|
||||
const type = file.type;
|
||||
if (type == folderType) {
|
||||
file.isFolder = true;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:title="$t('machine.cmdConfig')"
|
||||
v-model="dialogVisible"
|
||||
:show-close="false"
|
||||
width="600px"
|
||||
size="40%"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
@@ -78,7 +78,11 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('machine.relateMachine')">
|
||||
<tag-tree-check height="calc(100vh - 430px)" :tag-type="TagResourceTypeEnum.MachineAuthCert.value" v-model="form.codePaths" />
|
||||
<tag-tree-check
|
||||
height="calc(100vh - 430px)"
|
||||
:tag-type="`${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`"
|
||||
v-model="form.codePaths"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
||||
@@ -121,10 +121,10 @@
|
||||
|
||||
<el-dialog width="400px" :title="$t('mongo.createDbAndColl')" v-model="createDbDialog.visible" :destroy-on-close="true">
|
||||
<el-form :model="createDbDialog.form" label-width="auto">
|
||||
<el-form-item prop="dbName" :title="$t('mongo.dbName')" required>
|
||||
<el-form-item prop="dbName" :label="$t('mongo.dbName')" required>
|
||||
<el-input v-model="createDbDialog.form.dbName" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="collectionName" :title="$t('mongo.collName')" required>
|
||||
<el-form-item prop="collectionName" :label="$t('mongo.collName')" required>
|
||||
<el-input v-model="createDbDialog.form.collectionName" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
<div style="text-align: center; margin-top: 10px"></div>
|
||||
|
||||
<el-dialog :title="$t('redis.addKey')" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-form ref="keyForm" label-width="auto">
|
||||
<el-form ref="keyForm" label-width="auto" :rules="keyFormRules" :model="newKeyDialog.keyInfo">
|
||||
<el-form-item prop="key" label="Key" required>
|
||||
<el-input v-model.trim="newKeyDialog.keyInfo.key"></el-input>
|
||||
</el-form-item>
|
||||
@@ -187,9 +187,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { redisApi } from './api';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { isTrue, notBlank, notNull } from '@/common/assert';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref, watch, useTemplateRef } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { isTrue, notNull } from '@/common/assert';
|
||||
import { copyToClipboard } from '@/common/utils/string';
|
||||
import { TagTreeNode, NodeType, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
@@ -202,13 +202,21 @@ import { RedisInst } from './redis';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nOperateSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nFormValidate, useI18nOperateSuccessMsg, useI18nPleaseInput } from '@/hooks/useI18n';
|
||||
|
||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const contextmenuRef = ref();
|
||||
const keyFormRules = {
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: useI18nPleaseInput('Key'),
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const cmCopyKey = new ContextmenuItem('copyValue', 'Copy')
|
||||
.withIcon('CopyDocument')
|
||||
@@ -301,8 +309,11 @@ const treeProps = {
|
||||
|
||||
const defaultCount = 250;
|
||||
|
||||
const contextmenuRef = ref();
|
||||
const keyTreeRef: any = ref(null);
|
||||
const tagTreeRef: any = ref(null);
|
||||
const keyFormRef = useTemplateRef('keyForm');
|
||||
|
||||
const redisInst: Ref<RedisInst> = ref(new RedisInst());
|
||||
|
||||
const state = reactive({
|
||||
@@ -573,9 +584,9 @@ const cancelNewKey = () => {
|
||||
};
|
||||
|
||||
const newKey = async () => {
|
||||
await useI18nFormValidate(keyFormRef);
|
||||
const keyInfo = state.newKeyDialog.keyInfo;
|
||||
const key = keyInfo.key;
|
||||
notBlank(key, t('redis.keyNotEmpty'));
|
||||
|
||||
showKeyDetail(
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
@@ -344,7 +344,7 @@ const allowDrag = (node: any) => {
|
||||
const tagType = node.data.type;
|
||||
return (
|
||||
tagType == TagResourceTypeEnum.Tag.value ||
|
||||
tagType == TagResourceTypeEnum.Db.value ||
|
||||
tagType == TagResourceTypeEnum.DbInstance.value ||
|
||||
tagType == TagResourceTypeEnum.Redis.value ||
|
||||
tagType == TagResourceTypeEnum.Machine.value ||
|
||||
tagType == TagResourceTypeEnum.Mongo.value
|
||||
|
||||
@@ -7,7 +7,7 @@ export const tagApi = {
|
||||
delTagTree: Api.newDelete('/tag-trees/{id}'),
|
||||
movingTag: Api.newPost('/tag-trees/moving'),
|
||||
|
||||
getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
|
||||
getResourceTagPaths: Api.newGet('/tag-trees/resources/tag-paths'),
|
||||
countTagResource: Api.newGet('/tag-trees/resources/count'),
|
||||
getRelateTagIds: Api.newGet('/tag-trees/relate/{relateType}/{relateId}'),
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
ref="relatePageTableRef"
|
||||
:pageable="false"
|
||||
:page-api="accountApi.roles"
|
||||
v-model:query-form="releateQuery as any"
|
||||
v-model:query-form="releateQuery"
|
||||
:columns="relatedColumns"
|
||||
:tool-button="false"
|
||||
lazy
|
||||
@@ -133,6 +133,8 @@ const state = reactive({
|
||||
},
|
||||
releateQuery: {
|
||||
id: 0, //账号id
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
},
|
||||
showResourceDialog: {
|
||||
title: '',
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
<el-dialog :title="title" :destroy-on-close="true" v-model="dialogVisible" width="800px">
|
||||
<el-form :model="form" :inline="true" ref="menuForm" :rules="rules" label-width="auto">
|
||||
<el-row :gutter="35">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item class="w100" prop="type" :label="$t('common.type')" required>
|
||||
<enum-select :enums="ResourceTypeEnum" v-model="form.type" :disabled="typeDisabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item class="w100" prop="name" :label="$t('common.name')" required>
|
||||
<el-input v-model.trim="form.name" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item class="w100" prop="code" label="path|code">
|
||||
<template #label>
|
||||
path|code
|
||||
@@ -26,12 +26,12 @@
|
||||
<el-input v-model.trim="form.code" :placeholder="$t('system.menu.menuCodePlaceholder')" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100" :label="$t('system.menu.icon')">
|
||||
<icon-selector v-model="form.meta.icon" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100">
|
||||
<template #label>
|
||||
{{ $t('system.menu.routerName') }}
|
||||
@@ -44,7 +44,7 @@
|
||||
<el-input v-model.trim="form.meta.routeName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100" prop="code">
|
||||
<template #label>
|
||||
{{ $t('system.menu.componentPath') }}
|
||||
@@ -57,7 +57,7 @@
|
||||
<el-input v-model.trim="form.meta.component"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100" prop="isKeepAlive">
|
||||
<template #label>
|
||||
{{ $t('system.menu.isCache') }}
|
||||
@@ -72,7 +72,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100">
|
||||
<template #label>
|
||||
{{ $t('system.menu.isHide') }}
|
||||
@@ -87,14 +87,14 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100" prop="code" :label="$t('system.menu.tagIsDelete')">
|
||||
<el-select v-model="form.meta.isAffix" class="w100">
|
||||
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue">
|
||||
<el-form-item class="w100" prop="linkType">
|
||||
<template #label>
|
||||
{{ $t('system.menu.externalLink') }}
|
||||
@@ -111,7 +111,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue && form.meta.linkType > 0">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="form.type === menuTypeValue && form.meta.linkType > 0">
|
||||
<el-form-item prop="code" :label="$t('system.menu.linkAddress')" class="w100">
|
||||
<el-input v-model.trim="form.meta.link" :placeholder="$t('system.menu.linkPlaceholder')"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -3,8 +3,8 @@ module mayfly-go
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.16
|
||||
gitee.com/liuzongyang/libpq v1.0.9
|
||||
gitee.com/chunanyong/dm v1.8.17
|
||||
gitee.com/liuzongyang/libpq v1.10.11
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
@@ -32,9 +32,9 @@ require (
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/veops/go-ansiterm v0.0.5
|
||||
go.mongodb.org/mongo-driver v1.16.0 // mongo
|
||||
golang.org/x/crypto v0.29.0 // ssh
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.9.0
|
||||
golang.org/x/crypto v0.31.0 // ssh
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sync v0.10.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
// gorm
|
||||
@@ -93,8 +93,8 @@ require (
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
|
||||
@@ -4,23 +4,19 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"mayfly-go/pkg/config"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/middleware"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/static"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 初始化路由函数
|
||||
type InitRouterFunc func(router *gin.RouterGroup)
|
||||
|
||||
var (
|
||||
initRouterFuncs = make([]InitRouterFunc, 0)
|
||||
)
|
||||
|
||||
// 添加初始化路由函数,由各个默认自行添加
|
||||
func AddInitRouterFunc(initRouterFunc InitRouterFunc) {
|
||||
initRouterFuncs = append(initRouterFuncs, initRouterFunc)
|
||||
type RouterApi interface {
|
||||
// ReqConfs 获取请求配置信息
|
||||
ReqConfs() *req.Confs
|
||||
}
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
@@ -46,11 +42,17 @@ func InitRouter() *gin.Engine {
|
||||
|
||||
// 设置路由组
|
||||
api := router.Group(serverConfig.ContextPath + "/api")
|
||||
// 调用所有模块注册的初始化路由函数
|
||||
for _, initRouterFunc := range initRouterFuncs {
|
||||
initRouterFunc(api)
|
||||
|
||||
// 获取所有实现了RouterApi接口的实例,并注册对应路由
|
||||
ras := ioc.GetBeansByType[RouterApi](reflect.TypeOf((*RouterApi)(nil)).Elem())
|
||||
for _, ra := range ras {
|
||||
confs := ra.ReqConfs()
|
||||
if group := confs.Group; group != "" {
|
||||
req.BatchSetGroup(api.Group(group), confs.Confs)
|
||||
} else {
|
||||
req.BatchSetGroup(api, confs.Confs)
|
||||
}
|
||||
}
|
||||
initRouterFuncs = nil
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@ import (
|
||||
"mayfly-go/internal/auth/api/form"
|
||||
"mayfly-go/internal/auth/config"
|
||||
"mayfly-go/internal/auth/imsg"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
"mayfly-go/internal/auth/pkg/captcha"
|
||||
"mayfly-go/internal/auth/pkg/otp"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/captcha"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/otp"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
@@ -24,8 +23,24 @@ import (
|
||||
)
|
||||
|
||||
type AccountLogin struct {
|
||||
AccountApp sysapp.Account `inject:""`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
accountApp sysapp.Account `inject:"T"`
|
||||
}
|
||||
|
||||
func (a *AccountLogin) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 用户账号密码登录
|
||||
req.NewPost("/login", a.Login).Log(req.NewLogSaveI(imsg.LogAccountLogin)).DontNeedToken(),
|
||||
|
||||
req.NewGet("/refreshToken", a.RefreshToken).DontNeedToken(),
|
||||
|
||||
// 用户退出登录
|
||||
req.NewPost("/logout", a.Logout),
|
||||
|
||||
// 用户otp双因素校验
|
||||
req.NewPost("/otp-verify", a.OtpVerify).DontNeedToken(),
|
||||
}
|
||||
|
||||
return req.NewConfs("/auth/accounts", reqs[:]...)
|
||||
}
|
||||
|
||||
/** 用户账号密码登录 **/
|
||||
@@ -51,7 +66,7 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
|
||||
biz.ErrIsNilAppendErr(err, "decryption password error: %s")
|
||||
|
||||
account := &sysentity.Account{Username: username}
|
||||
err = a.AccountApp.GetByCond(model.NewModelCond(account).Columns("Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"))
|
||||
err = a.accountApp.GetByCond(model.NewModelCond(account).Columns("Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"))
|
||||
|
||||
failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
|
||||
nowFailCount := cache.GetInt(failCountKey)
|
||||
@@ -109,7 +124,7 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
||||
update := &sysentity.Account{OtpSecret: otpSecret}
|
||||
update.Id = accountId
|
||||
biz.ErrIsNil(update.OtpSecretEncrypt())
|
||||
biz.ErrIsNil(a.AccountApp.Update(context.Background(), update))
|
||||
biz.ErrIsNil(a.accountApp.Update(context.Background(), update))
|
||||
}
|
||||
|
||||
la := &sysentity.Account{Username: otpInfo.Username}
|
||||
|
||||
10
server/internal/auth/api/api.go
Normal file
10
server/internal/auth/api/api.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import "mayfly-go/pkg/ioc"
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(new(AccountLogin))
|
||||
ioc.Register(new(LdapLogin))
|
||||
ioc.Register(new(Oauth2Login))
|
||||
ioc.Register(new(Captcha))
|
||||
}
|
||||
25
server/internal/auth/api/captcha.go
Normal file
25
server/internal/auth/api/captcha.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/auth/pkg/captcha"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
|
||||
type Captcha struct {
|
||||
}
|
||||
|
||||
func (c *Captcha) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("", c.GenerateCaptcha).DontNeedToken(),
|
||||
}
|
||||
|
||||
return req.NewConfs("/sys/captcha", reqs[:]...)
|
||||
}
|
||||
|
||||
func (c *Captcha) GenerateCaptcha(rc *req.Ctx) {
|
||||
id, image, err := captcha.Generate()
|
||||
biz.ErrIsNilAppendErr(err, "failed to generate the CAPTCHA: %s")
|
||||
rc.ResData = collx.M{"base64Captcha": image, "cid": id}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/auth/config"
|
||||
"mayfly-go/internal/auth/imsg"
|
||||
"mayfly-go/internal/auth/pkg/otp"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgentity "mayfly-go/internal/msg/domain/entity"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/otp"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
|
||||
@@ -7,12 +7,11 @@ import (
|
||||
"mayfly-go/internal/auth/api/form"
|
||||
"mayfly-go/internal/auth/config"
|
||||
"mayfly-go/internal/auth/imsg"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
"mayfly-go/internal/auth/pkg/captcha"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/captcha"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -28,8 +27,16 @@ import (
|
||||
)
|
||||
|
||||
type LdapLogin struct {
|
||||
AccountApp sysapp.Account `inject:""`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
accountApp sysapp.Account `inject:"T"`
|
||||
}
|
||||
|
||||
func (l *LdapLogin) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("/enabled", l.GetLdapEnabled).DontNeedToken(),
|
||||
req.NewPost("/login", l.Login).Log(req.NewLogSaveI(imsg.LogLdapLogin)).DontNeedToken(),
|
||||
}
|
||||
|
||||
return req.NewConfs("/auth/ldap", reqs[:]...)
|
||||
}
|
||||
|
||||
// @router /auth/ldap/enabled [get]
|
||||
@@ -80,7 +87,7 @@ func (a *LdapLogin) Login(rc *req.Ctx) {
|
||||
|
||||
func (a *LdapLogin) getUser(userName string, cols ...string) (*sysentity.Account, error) {
|
||||
account := &sysentity.Account{Username: userName}
|
||||
if err := a.AccountApp.GetByCond(model.NewModelCond(account).Columns(cols...)); err != nil {
|
||||
if err := a.accountApp.GetByCond(model.NewModelCond(account).Columns(cols...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return account, nil
|
||||
@@ -90,10 +97,10 @@ func (a *LdapLogin) createUser(userName, displayName string) {
|
||||
account := &sysentity.Account{Username: userName}
|
||||
account.FillBaseInfo(model.IdGenTypeNone, nil)
|
||||
account.Name = displayName
|
||||
biz.ErrIsNil(a.AccountApp.Create(context.TODO(), account))
|
||||
biz.ErrIsNil(a.accountApp.Create(context.TODO(), account))
|
||||
// 将 LADP 用户本地密码设置为空,不允许本地登录
|
||||
account.Password = cryptox.PwdHash("")
|
||||
biz.ErrIsNil(a.AccountApp.Update(context.TODO(), account))
|
||||
biz.ErrIsNil(a.accountApp.Update(context.TODO(), account))
|
||||
}
|
||||
|
||||
func (a *LdapLogin) getOrCreateUserWithLdap(ctx context.Context, userName string, password string, cols ...string) (*sysentity.Account, error) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"mayfly-go/internal/auth/config"
|
||||
"mayfly-go/internal/auth/domain/entity"
|
||||
"mayfly-go/internal/auth/imsg"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
@@ -29,9 +28,28 @@ import (
|
||||
)
|
||||
|
||||
type Oauth2Login struct {
|
||||
Oauth2App application.Oauth2 `inject:""`
|
||||
AccountApp sysapp.Account `inject:""`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
oauth2App application.Oauth2 `inject:"T"`
|
||||
accountApp sysapp.Account `inject:"T"`
|
||||
}
|
||||
|
||||
func (o *Oauth2Login) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("/config", o.Oauth2Config).DontNeedToken(),
|
||||
|
||||
// oauth2登录
|
||||
req.NewGet("/login", o.OAuth2Login).DontNeedToken(),
|
||||
|
||||
req.NewGet("/bind", o.OAuth2Bind),
|
||||
|
||||
// oauth2回调地址
|
||||
req.NewGet("/callback", o.OAuth2Callback).Log(req.NewLogSaveI(imsg.LogOauth2Callback)).DontNeedToken(),
|
||||
|
||||
req.NewGet("/status", o.Oauth2Status),
|
||||
|
||||
req.NewGet("/unbind", o.Oauth2Unbind).Log(req.NewLogSaveI(imsg.LogOauth2Unbind)),
|
||||
}
|
||||
|
||||
return req.NewConfs("/auth/oauth2", reqs[:]...)
|
||||
}
|
||||
|
||||
func (a *Oauth2Login) OAuth2Login(rc *req.Ctx) {
|
||||
@@ -100,22 +118,22 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
|
||||
|
||||
account := new(sysentity.Account)
|
||||
account.Id = accountId
|
||||
err = a.AccountApp.GetByCond(model.NewModelCond(account).Columns("username"))
|
||||
err = a.accountApp.GetByCond(model.NewModelCond(account).Columns("username"))
|
||||
biz.ErrIsNilAppendErr(err, "this account does not exist")
|
||||
rc.ReqParam = collx.Kvs("username", account.Username, "type", "bind")
|
||||
|
||||
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||
err = a.oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||
AccountId: accountId,
|
||||
}, "account_id", "identity")
|
||||
biz.IsTrue(err != nil, "the account has been linked by another user")
|
||||
|
||||
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||
err = a.oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||
Identity: userId,
|
||||
}, "account_id", "identity")
|
||||
biz.IsTrue(err != nil, "you are bound to another account")
|
||||
|
||||
now := time.Now()
|
||||
err = a.Oauth2App.BindOAuthAccount(&entity.Oauth2Account{
|
||||
err = a.oauth2App.BindOAuthAccount(&entity.Oauth2Account{
|
||||
AccountId: accountId,
|
||||
Identity: userId,
|
||||
CreateTime: &now,
|
||||
@@ -136,7 +154,7 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
|
||||
func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oauth2Login) {
|
||||
// 查询用户是否存在
|
||||
oauthAccount := &entity.Oauth2Account{Identity: userId}
|
||||
err := a.Oauth2App.GetOAuthAccount(oauthAccount, "account_id", "identity")
|
||||
err := a.oauth2App.GetOAuthAccount(oauthAccount, "account_id", "identity")
|
||||
ctx := rc.MetaCtx
|
||||
var accountId uint64
|
||||
isFirst := false
|
||||
@@ -156,9 +174,9 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
|
||||
Name: userId,
|
||||
Username: userId,
|
||||
}
|
||||
biz.ErrIsNil(a.AccountApp.Create(context.TODO(), account))
|
||||
biz.ErrIsNil(a.accountApp.Create(context.TODO(), account))
|
||||
// 绑定
|
||||
err := a.Oauth2App.BindOAuthAccount(&entity.Oauth2Account{
|
||||
err := a.oauth2App.BindOAuthAccount(&entity.Oauth2Account{
|
||||
AccountId: account.Id,
|
||||
Identity: oauthAccount.Identity,
|
||||
CreateTime: &now,
|
||||
@@ -172,7 +190,7 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
|
||||
}
|
||||
|
||||
// 进行登录
|
||||
account, err := a.AccountApp.GetById(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, "get user info error: %s")
|
||||
|
||||
clientIp := getIpAndRegion(rc)
|
||||
@@ -207,7 +225,7 @@ func (a *Oauth2Login) Oauth2Status(ctx *req.Ctx) {
|
||||
oauth2LoginConfig := config.GetOauth2Login()
|
||||
res.Enable = oauth2LoginConfig.Enable
|
||||
if res.Enable {
|
||||
err := a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||
err := a.oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||
AccountId: ctx.GetLoginAccount().Id,
|
||||
}, "account_id", "identity")
|
||||
res.Bind = err == nil
|
||||
@@ -217,7 +235,7 @@ func (a *Oauth2Login) Oauth2Status(ctx *req.Ctx) {
|
||||
}
|
||||
|
||||
func (a *Oauth2Login) Oauth2Unbind(rc *req.Ctx) {
|
||||
a.Oauth2App.Unbind(rc.GetLoginAccount().Id)
|
||||
a.oauth2App.Unbind(rc.GetLoginAccount().Id)
|
||||
}
|
||||
|
||||
// 获取oauth2登录配置信息,因为有些字段是敏感字段,故单独使用接口获取
|
||||
|
||||
@@ -16,20 +16,20 @@ type Oauth2 interface {
|
||||
}
|
||||
|
||||
type oauth2AppImpl struct {
|
||||
Oauth2AccountRepo repository.Oauth2Account `inject:""`
|
||||
oauth2AccountRepo repository.Oauth2Account `inject:"T"`
|
||||
}
|
||||
|
||||
func (a *oauth2AppImpl) GetOAuthAccount(condition *entity.Oauth2Account, cols ...string) error {
|
||||
return a.Oauth2AccountRepo.GetByCond(model.NewModelCond(condition).Columns(cols...))
|
||||
return a.oauth2AccountRepo.GetByCond(model.NewModelCond(condition).Columns(cols...))
|
||||
}
|
||||
|
||||
func (a *oauth2AppImpl) BindOAuthAccount(e *entity.Oauth2Account) error {
|
||||
if e.Id == 0 {
|
||||
return a.Oauth2AccountRepo.Insert(context.Background(), e)
|
||||
return a.oauth2AccountRepo.Insert(context.Background(), e)
|
||||
}
|
||||
return a.Oauth2AccountRepo.UpdateById(context.Background(), e)
|
||||
return a.oauth2AccountRepo.UpdateById(context.Background(), e)
|
||||
}
|
||||
|
||||
func (a *oauth2AppImpl) Unbind(accountId uint64) {
|
||||
a.Oauth2AccountRepo.DeleteByCond(context.Background(), &entity.Oauth2Account{AccountId: accountId})
|
||||
a.oauth2AccountRepo.DeleteByCond(context.Background(), &entity.Oauth2Account{AccountId: accountId})
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ type oauth2AccountRepoImpl struct {
|
||||
}
|
||||
|
||||
func newAuthAccountRepo() repository.Oauth2Account {
|
||||
return &oauth2AccountRepoImpl{base.RepoImpl[*entity.Oauth2Account]{M: new(entity.Oauth2Account)}}
|
||||
return &oauth2AccountRepoImpl{}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/auth/api"
|
||||
"mayfly-go/internal/auth/application"
|
||||
"mayfly-go/internal/auth/infrastructure/persistence"
|
||||
"mayfly-go/internal/auth/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
api.InitIoc()
|
||||
})
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/auth/api"
|
||||
"mayfly-go/internal/auth/imsg"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Init(router *gin.RouterGroup) {
|
||||
accountLogin := new(api.AccountLogin)
|
||||
biz.ErrIsNil(ioc.Inject(accountLogin))
|
||||
|
||||
ldapLogin := new(api.LdapLogin)
|
||||
biz.ErrIsNil(ioc.Inject(ldapLogin))
|
||||
|
||||
oauth2Login := new(api.Oauth2Login)
|
||||
biz.ErrIsNil(ioc.Inject(oauth2Login))
|
||||
|
||||
rg := router.Group("/auth")
|
||||
|
||||
reqs := [...]*req.Conf{
|
||||
|
||||
// 用户账号密码登录
|
||||
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSaveI(imsg.LogAccountLogin)).DontNeedToken(),
|
||||
|
||||
req.NewGet("/accounts/refreshToken", accountLogin.RefreshToken).DontNeedToken(),
|
||||
|
||||
// 用户退出登录
|
||||
req.NewPost("/accounts/logout", accountLogin.Logout),
|
||||
|
||||
// 用户otp双因素校验
|
||||
req.NewPost("/accounts/otp-verify", accountLogin.OtpVerify).DontNeedToken(),
|
||||
|
||||
/*--------oauth2登录相关----------*/
|
||||
|
||||
req.NewGet("/oauth2-config", oauth2Login.Oauth2Config).DontNeedToken(),
|
||||
|
||||
// oauth2登录
|
||||
req.NewGet("/oauth2/login", oauth2Login.OAuth2Login).DontNeedToken(),
|
||||
|
||||
req.NewGet("/oauth2/bind", oauth2Login.OAuth2Bind),
|
||||
|
||||
// oauth2回调地址
|
||||
req.NewGet("/oauth2/callback", oauth2Login.OAuth2Callback).Log(req.NewLogSaveI(imsg.LogOauth2Callback)).DontNeedToken(),
|
||||
|
||||
req.NewGet("/oauth2/status", oauth2Login.Oauth2Status),
|
||||
|
||||
req.NewGet("/oauth2/unbind", oauth2Login.Oauth2Unbind).Log(req.NewLogSaveI(imsg.LogOauth2Unbind)),
|
||||
|
||||
// LDAP 登录
|
||||
req.NewGet("/ldap/enabled", ldapLogin.GetLdapEnabled).DontNeedToken(),
|
||||
req.NewPost("/ldap/login", ldapLogin.Login).Log(req.NewLogSaveI(imsg.LogLdapLogin)).DontNeedToken(),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(rg, reqs[:])
|
||||
}
|
||||
7
server/internal/common/api/api.go
Normal file
7
server/internal/common/api/api.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package api
|
||||
|
||||
import "mayfly-go/pkg/ioc"
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(new(Common))
|
||||
}
|
||||
@@ -9,8 +9,17 @@ import (
|
||||
type Common struct {
|
||||
}
|
||||
|
||||
func (c *Common) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 获取公钥
|
||||
req.NewGet("/public-key", c.RasPublicKey).DontNeedToken(),
|
||||
}
|
||||
|
||||
return req.NewConfs("/common", reqs[:]...)
|
||||
}
|
||||
|
||||
func (i *Common) RasPublicKey(rc *req.Ctx) {
|
||||
publicKeyStr, err := cryptox.GetRsaPublicKey()
|
||||
biz.ErrIsNilAppendErr(err, "rsa生成公私钥失败")
|
||||
biz.ErrIsNilAppendErr(err, "rsa - failed to genenrate public key")
|
||||
rc.ResData = publicKeyStr
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ const (
|
||||
// RedisConnExpireTime = 2 * time.Minute
|
||||
// MongoConnExpireTime = 2 * time.Minute
|
||||
|
||||
ResourceTypeMachine int8 = 1
|
||||
ResourceTypeDb int8 = 2
|
||||
ResourceTypeRedis int8 = 3
|
||||
ResourceTypeMongo int8 = 4
|
||||
ResourceTypeMachine int8 = 1
|
||||
ResourceTypeDbInstance int8 = 2
|
||||
ResourceTypeRedis int8 = 3
|
||||
ResourceTypeMongo int8 = 4
|
||||
|
||||
// imsg起始编号
|
||||
ImsgNumSys = 10000
|
||||
|
||||
@@ -2,9 +2,11 @@ package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/common/router"
|
||||
"mayfly-go/internal/common/api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
initialize.AddInitIocFunc(func() {
|
||||
api.InitIoc()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/common/api"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitCommonRouter(router *gin.RouterGroup) {
|
||||
common := router.Group("common")
|
||||
c := &api.Common{}
|
||||
{
|
||||
// 获取公钥
|
||||
req.NewGet("public-key", c.RasPublicKey).DontNeedToken().Group(common)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package router
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func Init(router *gin.RouterGroup) {
|
||||
InitCommonRouter(router)
|
||||
}
|
||||
13
server/internal/db/api/api.go
Normal file
13
server/internal/db/api/api.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package api
|
||||
|
||||
import "mayfly-go/pkg/ioc"
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(new(Dashbord))
|
||||
ioc.Register(new(Db))
|
||||
ioc.Register(new(Instance))
|
||||
ioc.Register(new(DbSqlExec))
|
||||
ioc.Register(new(DbSql))
|
||||
ioc.Register(new(DataSyncTask))
|
||||
ioc.Register(new(DbTransferTask))
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/application"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -9,14 +8,21 @@ import (
|
||||
)
|
||||
|
||||
type Dashbord struct {
|
||||
TagTreeApp tagapp.TagTree `inject:""`
|
||||
DbApp application.Db `inject:""`
|
||||
tagTreeApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *Dashbord) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("dashbord", d.Dashbord),
|
||||
}
|
||||
|
||||
return req.NewConfs("/dbs", reqs[:]...)
|
||||
}
|
||||
|
||||
func (m *Dashbord) Dashbord(rc *req.Ctx) {
|
||||
accountId := rc.GetLoginAccount().Id
|
||||
|
||||
tagCodePaths := m.TagTreeApp.GetAccountTagCodePaths(accountId, tagentity.TagTypeDbName, "")
|
||||
tagCodePaths := m.tagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{Types: collx.AsArray(tagentity.TagTypeDb)}).GetCodePaths()
|
||||
|
||||
rc.ResData = collx.M{
|
||||
"dbNum": len(tagCodePaths),
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"mayfly-go/internal/db/application/dto"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/dbm/sqlparser"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/imsg"
|
||||
"mayfly-go/internal/event"
|
||||
@@ -26,9 +25,7 @@ import (
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -36,11 +33,46 @@ import (
|
||||
)
|
||||
|
||||
type Db struct {
|
||||
InstanceApp application.Instance `inject:"DbInstanceApp"`
|
||||
DbApp application.Db `inject:""`
|
||||
DbSqlExecApp application.DbSqlExec `inject:""`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
instanceApp application.Instance `inject:"T"`
|
||||
dbApp application.Db `inject:"T"`
|
||||
dbSqlExecApp application.DbSqlExec `inject:"T"`
|
||||
msgApp msgapp.Msg `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *Db) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 获取数据库列表
|
||||
req.NewGet("", d.Dbs),
|
||||
|
||||
req.NewPost("", d.Save).Log(req.NewLogSaveI(imsg.LogDbSave)),
|
||||
|
||||
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSaveI(imsg.LogDbDelete)),
|
||||
|
||||
req.NewGet(":dbId/t-create-ddl", d.GetTableDDL),
|
||||
|
||||
req.NewGet(":dbId/version", d.GetVersion),
|
||||
|
||||
req.NewGet(":dbId/pg/schemas", d.GetSchemas),
|
||||
|
||||
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLogI(imsg.LogDbRunSql)),
|
||||
|
||||
req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSaveI(imsg.LogDbRunSqlFile)).RequiredPermissionCode("db:sqlscript:run"),
|
||||
|
||||
req.NewGet(":dbId/dump", d.DumpSql).Log(req.NewLogSaveI(imsg.LogDbDump)).NoRes(),
|
||||
|
||||
req.NewGet(":dbId/t-infos", d.TableInfos),
|
||||
|
||||
req.NewGet(":dbId/t-index", d.TableIndex),
|
||||
|
||||
req.NewGet(":dbId/c-metadata", d.ColumnMA),
|
||||
|
||||
req.NewGet(":dbId/hint-tables", d.HintTables),
|
||||
|
||||
req.NewPost(":dbId/copy-table", d.CopyTable),
|
||||
}
|
||||
|
||||
return req.NewConfs("/dbs", reqs[:]...)
|
||||
}
|
||||
|
||||
// @router /api/dbs [get]
|
||||
@@ -48,18 +80,21 @@ func (d *Db) Dbs(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
|
||||
|
||||
// 不存在可访问标签id,即没有可操作数据
|
||||
codes := d.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, int8(tagentity.TagTypeDbName), queryCond.TagPath)
|
||||
if len(codes) == 0 {
|
||||
tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert, tagentity.TagTypeDb)),
|
||||
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
||||
})
|
||||
if len(tags) == 0 {
|
||||
rc.ResData = model.EmptyPageResult[any]()
|
||||
return
|
||||
}
|
||||
queryCond.Codes = codes
|
||||
queryCond.Codes = tags.GetCodes()
|
||||
|
||||
var dbvos []*vo.DbListVO
|
||||
res, err := d.DbApp.GetPageList(queryCond, page, &dbvos)
|
||||
res, err := d.dbApp.GetPageList(queryCond, page, &dbvos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
instances, _ := d.InstanceApp.GetByIds(collx.ArrayMap(dbvos, func(i *vo.DbListVO) uint64 {
|
||||
instances, _ := d.instanceApp.GetByIds(collx.ArrayMap(dbvos, func(i *vo.DbListVO) uint64 {
|
||||
return i.InstanceId
|
||||
}))
|
||||
instancesMap := collx.ArrayToMap(instances, func(i *entity.DbInstance) uint64 {
|
||||
@@ -84,7 +119,7 @@ func (d *Db) Save(rc *req.Ctx) {
|
||||
|
||||
rc.ReqParam = form
|
||||
|
||||
biz.ErrIsNil(d.DbApp.SaveDb(rc.MetaCtx, db))
|
||||
biz.ErrIsNil(d.dbApp.SaveDb(rc.MetaCtx, db))
|
||||
}
|
||||
|
||||
func (d *Db) DeleteDb(rc *req.Ctx) {
|
||||
@@ -94,7 +129,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
|
||||
|
||||
ctx := rc.MetaCtx
|
||||
for _, v := range ids {
|
||||
biz.ErrIsNil(d.DbApp.Delete(ctx, cast.ToUint64(v)))
|
||||
biz.ErrIsNil(d.dbApp.Delete(ctx, cast.ToUint64(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +139,9 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
form := req.BindJsonAndValid(rc, new(form.DbSqlExecForm))
|
||||
|
||||
dbId := getDbId(rc)
|
||||
dbConn, err := d.DbApp.GetDbConn(dbId, form.Db)
|
||||
dbConn, err := d.dbApp.GetDbConn(dbId, form.Db)
|
||||
biz.ErrIsNil(err)
|
||||
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%s")
|
||||
biz.ErrIsNilAppendErr(d.tagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%s")
|
||||
|
||||
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, dbConn.Info.CodePath[0])
|
||||
sqlStr, err := cryptox.AesDecryptByLa(form.Sql, rc.GetLoginAccount())
|
||||
@@ -115,7 +150,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
rc.ReqParam = fmt.Sprintf("%s %s\n-> %s", dbConn.Info.GetLogDesc(), form.ExecId, sqlStr)
|
||||
biz.NotEmpty(form.Sql, "sql cannot be empty")
|
||||
|
||||
execReq := &application.DbSqlExecReq{
|
||||
execReq := &dto.DbSqlExecReq{
|
||||
DbId: dbId,
|
||||
Db: form.Db,
|
||||
Remark: form.Remark,
|
||||
@@ -127,7 +162,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
ctx, cancel := context.WithTimeout(rc.MetaCtx, time.Duration(config.GetDbms().SqlExecTl)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
execRes, err := d.DbSqlExecApp.Exec(ctx, execReq)
|
||||
execRes, err := d.dbSqlExecApp.Exec(ctx, execReq)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = execRes
|
||||
}
|
||||
@@ -155,52 +190,17 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
dbName := getDbName(rc)
|
||||
clientId := rc.Query("clientId")
|
||||
|
||||
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
|
||||
dbConn, err := d.dbApp.GetDbConn(dbId, dbName)
|
||||
biz.ErrIsNil(err)
|
||||
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%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() {
|
||||
if err := recover(); err != nil {
|
||||
errInfo := anyx.ToString(err)
|
||||
if len(errInfo) > 300 {
|
||||
errInfo = errInfo[:300] + "..."
|
||||
}
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] execution failure: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)).WithClientId(clientId))
|
||||
}
|
||||
}()
|
||||
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
laId := rc.GetLoginAccount().Id
|
||||
defer ws.SendJsonMsg(ws.UserId(laId), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: true,
|
||||
}).WithCategory(progressCategory))
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
|
||||
err = sqlparser.SQLSplit(file, func(sql string) error {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ws.SendJsonMsg(ws.UserId(laId), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: false,
|
||||
}).WithCategory(progressCategory))
|
||||
default:
|
||||
}
|
||||
|
||||
executedStatements++
|
||||
_, err = dbConn.Exec(sql)
|
||||
return err
|
||||
})
|
||||
|
||||
biz.ErrIsNilAppendErr(err, "%s")
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), fmt.Sprintf("sql脚本执行完成:%s", rc.ReqParam)).WithClientId(clientId))
|
||||
biz.ErrIsNil(d.dbSqlExecApp.ExecReader(rc.MetaCtx, &dto.SqlReaderExec{
|
||||
Reader: file,
|
||||
Filename: filename,
|
||||
DbConn: dbConn,
|
||||
ClientId: clientId,
|
||||
}))
|
||||
}
|
||||
|
||||
// 数据库dump
|
||||
@@ -223,9 +223,9 @@ func (d *Db) DumpSql(rc *req.Ctx) {
|
||||
needData := dumpType == "2" || dumpType == "3"
|
||||
|
||||
la := rc.GetLoginAccount()
|
||||
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
|
||||
dbConn, err := d.dbApp.GetDbConn(dbId, dbName)
|
||||
biz.ErrIsNil(err)
|
||||
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, dbConn.Info.CodePath...), "%s")
|
||||
biz.ErrIsNilAppendErr(d.tagApp.CanAccess(la.Id, dbConn.Info.CodePath...), "%s")
|
||||
|
||||
now := time.Now()
|
||||
filename := fmt.Sprintf("%s-%s.%s.sql%s", dbConn.Info.Name, dbName, now.Format("20060102150405"), extName)
|
||||
@@ -245,11 +245,11 @@ func (d *Db) DumpSql(rc *req.Ctx) {
|
||||
if len(msg) > 0 {
|
||||
msg = "DB dump error: " + msg
|
||||
rc.GetWriter().Write([]byte(msg))
|
||||
d.MsgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.DbDumpErr), msg))
|
||||
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.DbDumpErr), msg))
|
||||
}
|
||||
}()
|
||||
|
||||
biz.ErrIsNil(d.DbApp.DumpDb(rc.MetaCtx, &dto.DumpDb{
|
||||
biz.ErrIsNil(d.dbApp.DumpDb(rc.MetaCtx, &dto.DumpDb{
|
||||
DbId: dbId,
|
||||
DbName: dbName,
|
||||
Tables: tables,
|
||||
@@ -351,7 +351,7 @@ func (d *Db) CopyTable(rc *req.Ctx) {
|
||||
form := &form.DbCopyTableForm{}
|
||||
copy := req.BindJsonAndCopyTo[*dbi.DbCopyTable](rc, form, new(dbi.DbCopyTable))
|
||||
|
||||
conn, err := d.DbApp.GetDbConn(form.Id, form.Db)
|
||||
conn, err := d.dbApp.GetDbConn(form.Id, form.Db)
|
||||
biz.ErrIsNilAppendErr(err, "copy table error: %s")
|
||||
|
||||
err = conn.GetDialect().CopyTable(copy)
|
||||
@@ -374,7 +374,7 @@ func getDbName(rc *req.Ctx) string {
|
||||
}
|
||||
|
||||
func (d *Db) getDbConn(rc *req.Ctx) *dbi.DbConn {
|
||||
dc, err := d.DbApp.GetDbConn(getDbId(rc), getDbName(rc))
|
||||
dc, err := d.dbApp.GetDbConn(getDbId(rc), getDbName(rc))
|
||||
biz.ErrIsNil(err)
|
||||
return dc
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/imsg"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
@@ -15,19 +16,48 @@ import (
|
||||
)
|
||||
|
||||
type DataSyncTask struct {
|
||||
DataSyncTaskApp application.DataSyncTask `inject:"DbDataSyncTaskApp"`
|
||||
dataSyncTaskApp application.DataSyncTask `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 获取任务列表 /datasync
|
||||
req.NewGet("", d.Tasks),
|
||||
|
||||
req.NewGet(":taskId/logs", d.Logs).RequiredPermissionCode("db:sync:log"),
|
||||
|
||||
// 保存任务 /datasync/save
|
||||
req.NewPost("save", d.SaveTask).Log(req.NewLogSaveI(imsg.LogDataSyncSave)).RequiredPermissionCode("db:sync:save"),
|
||||
|
||||
// 获取单个详情 /datasync/:taskId
|
||||
req.NewGet(":taskId", d.GetTask),
|
||||
|
||||
// 删除任务 /datasync/:taskId/del
|
||||
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSaveI(imsg.LogDataSyncDelete)).RequiredPermissionCode("db:sync:del"),
|
||||
|
||||
// 启停用任务 /datasync/status
|
||||
req.NewPost(":taskId/status", d.ChangeStatus).Log(req.NewLogSaveI(imsg.LogDataSyncChangeStatus)).RequiredPermissionCode("db:sync:status"),
|
||||
|
||||
// 立即执行任务 /datasync/run
|
||||
req.NewPost(":taskId/run", d.Run),
|
||||
|
||||
// 停止正在执行中的任务
|
||||
req.NewPost(":taskId/stop", d.Stop),
|
||||
}
|
||||
|
||||
return req.NewConfs("/datasync/tasks", reqs[:]...)
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) Tasks(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DataSyncTaskQuery](rc, new(entity.DataSyncTaskQuery))
|
||||
res, err := d.DataSyncTaskApp.GetPageList(queryCond, page, new([]vo.DataSyncTaskListVO))
|
||||
res, err := d.dataSyncTaskApp.GetPageList(queryCond, page, new([]vo.DataSyncTaskListVO))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) Logs(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DataSyncLogQuery](rc, new(entity.DataSyncLogQuery))
|
||||
res, err := d.DataSyncTaskApp.GetTaskLogList(queryCond, page, new([]vo.DataSyncLogListVO))
|
||||
res, err := d.dataSyncTaskApp.GetTaskLogList(queryCond, page, new([]vo.DataSyncLogListVO))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
@@ -44,7 +74,7 @@ func (d *DataSyncTask) SaveTask(rc *req.Ctx) {
|
||||
form.DataSql = sql
|
||||
|
||||
rc.ReqParam = form
|
||||
biz.ErrIsNil(d.DataSyncTaskApp.Save(rc.MetaCtx, task))
|
||||
biz.ErrIsNil(d.dataSyncTaskApp.Save(rc.MetaCtx, task))
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) DeleteTask(rc *req.Ctx) {
|
||||
@@ -53,21 +83,21 @@ func (d *DataSyncTask) DeleteTask(rc *req.Ctx) {
|
||||
ids := strings.Split(taskId, ",")
|
||||
|
||||
for _, v := range ids {
|
||||
biz.ErrIsNil(d.DataSyncTaskApp.Delete(rc.MetaCtx, cast.ToUint64(v)))
|
||||
biz.ErrIsNil(d.dataSyncTaskApp.Delete(rc.MetaCtx, cast.ToUint64(v)))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
|
||||
form := &form.DataSyncTaskStatusForm{}
|
||||
task := req.BindJsonAndCopyTo[*entity.DataSyncTask](rc, form, new(entity.DataSyncTask))
|
||||
_ = d.DataSyncTaskApp.UpdateById(rc.MetaCtx, task)
|
||||
_ = d.dataSyncTaskApp.UpdateById(rc.MetaCtx, task)
|
||||
|
||||
if task.Status == entity.DataSyncTaskStatusEnable {
|
||||
task, err := d.DataSyncTaskApp.GetById(task.Id)
|
||||
task, err := d.dataSyncTaskApp.GetById(task.Id)
|
||||
biz.ErrIsNil(err, "task not found")
|
||||
d.DataSyncTaskApp.AddCronJob(rc.MetaCtx, task)
|
||||
d.dataSyncTaskApp.AddCronJob(rc.MetaCtx, task)
|
||||
} else {
|
||||
d.DataSyncTaskApp.RemoveCronJobById(task.Id)
|
||||
d.dataSyncTaskApp.RemoveCronJobById(task.Id)
|
||||
}
|
||||
// 记录请求日志
|
||||
rc.ReqParam = form
|
||||
@@ -76,7 +106,7 @@ func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
|
||||
func (d *DataSyncTask) Run(rc *req.Ctx) {
|
||||
taskId := d.getTaskId(rc)
|
||||
rc.ReqParam = taskId
|
||||
_ = d.DataSyncTaskApp.RunCronJob(rc.MetaCtx, taskId)
|
||||
_ = d.dataSyncTaskApp.RunCronJob(rc.MetaCtx, taskId)
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) Stop(rc *req.Ctx) {
|
||||
@@ -86,12 +116,12 @@ func (d *DataSyncTask) Stop(rc *req.Ctx) {
|
||||
task := new(entity.DataSyncTask)
|
||||
task.Id = taskId
|
||||
task.RunningState = entity.DataSyncTaskRunStateStop
|
||||
_ = d.DataSyncTaskApp.UpdateById(rc.MetaCtx, task)
|
||||
_ = d.dataSyncTaskApp.UpdateById(rc.MetaCtx, task)
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) GetTask(rc *req.Ctx) {
|
||||
taskId := d.getTaskId(rc)
|
||||
dbEntity, _ := d.DataSyncTaskApp.GetById(taskId)
|
||||
dbEntity, _ := d.dataSyncTaskApp.GetById(taskId)
|
||||
rc.ResData = dbEntity
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/application/dto"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/imsg"
|
||||
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
@@ -20,10 +21,35 @@ import (
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
InstanceApp application.Instance `inject:"DbInstanceApp"`
|
||||
DbApp application.Db `inject:""`
|
||||
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
instanceApp application.Instance `inject:"T"`
|
||||
dbApp application.Db `inject:"T"`
|
||||
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *Instance) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 获取数据库列表
|
||||
req.NewGet("", d.Instances),
|
||||
|
||||
req.NewPost("/test-conn", d.TestConn),
|
||||
|
||||
req.NewPost("", d.SaveInstance).Log(req.NewLogSaveI(imsg.LogDbInstSave)),
|
||||
|
||||
req.NewGet(":instanceId", d.GetInstance),
|
||||
|
||||
// 获取数据库实例的所有数据库名
|
||||
req.NewPost("/databases", d.GetDatabaseNames),
|
||||
|
||||
// 根据授权凭证名获取其所有库名
|
||||
req.NewGet("/databases/:ac", d.GetDatabaseNamesByAc),
|
||||
|
||||
req.NewGet(":instanceId/server-info", d.GetDbServer),
|
||||
|
||||
req.NewDelete(":instanceId", d.DeleteInstance).Log(req.NewLogSaveI(imsg.LogDbInstDelete)),
|
||||
}
|
||||
|
||||
return req.NewConfs("/instances", reqs[:]...)
|
||||
}
|
||||
|
||||
// Instances 获取数据库实例信息
|
||||
@@ -31,27 +57,31 @@ type Instance struct {
|
||||
func (d *Instance) Instances(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery))
|
||||
|
||||
tagCodePaths := d.TagApp.GetAccountTagCodePaths(rc.GetLoginAccount().Id, tagentity.TagTypeDbAuthCert, queryCond.TagPath)
|
||||
tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert)),
|
||||
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
||||
})
|
||||
// 不存在可操作的数据库,即没有可操作数据
|
||||
if len(tagCodePaths) == 0 {
|
||||
if len(tags) == 0 {
|
||||
rc.ResData = model.EmptyPageResult[any]()
|
||||
return
|
||||
}
|
||||
|
||||
dbInstCodes := tagentity.GetCodeByPath(tagentity.TagTypeDb, tagCodePaths...)
|
||||
tagCodePaths := tags.GetCodePaths()
|
||||
dbInstCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeDbInstance, tagCodePaths...)
|
||||
queryCond.Codes = dbInstCodes
|
||||
|
||||
var instvos []*vo.InstanceListVO
|
||||
res, err := d.InstanceApp.GetPageList(queryCond, page, &instvos)
|
||||
res, err := d.instanceApp.GetPageList(queryCond, page, &instvos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
// 填充授权凭证信息
|
||||
d.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodeByPath(tagentity.TagTypeDbAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
|
||||
d.resourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
|
||||
return vos
|
||||
})...)
|
||||
|
||||
// 填充标签信息
|
||||
d.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDb), collx.ArrayMap(instvos, func(insvo *vo.InstanceListVO) tagentity.ITagResource {
|
||||
d.tagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDbInstance), collx.ArrayMap(instvos, func(insvo *vo.InstanceListVO) tagentity.ITagResource {
|
||||
return insvo
|
||||
})...)
|
||||
|
||||
@@ -62,7 +92,7 @@ func (d *Instance) TestConn(rc *req.Ctx) {
|
||||
form := &form.InstanceForm{}
|
||||
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
|
||||
|
||||
biz.ErrIsNil(d.InstanceApp.TestConn(instance, form.AuthCerts[0]))
|
||||
biz.ErrIsNil(d.instanceApp.TestConn(instance, form.AuthCerts[0]))
|
||||
}
|
||||
|
||||
// SaveInstance 保存数据库实例信息
|
||||
@@ -72,7 +102,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, &dto.SaveDbInstance{
|
||||
id, err := d.instanceApp.SaveDbInstance(rc.MetaCtx, &dto.SaveDbInstance{
|
||||
DbInstance: instance,
|
||||
AuthCerts: form.AuthCerts,
|
||||
TagCodePaths: form.TagCodePaths,
|
||||
@@ -85,7 +115,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(dbId)
|
||||
dbEntity, err := d.instanceApp.GetById(dbId)
|
||||
biz.ErrIsNilAppendErr(err, "get db instance failed: %s")
|
||||
rc.ResData = dbEntity
|
||||
}
|
||||
@@ -98,7 +128,7 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) {
|
||||
ids := strings.Split(idsStr, ",")
|
||||
|
||||
for _, v := range ids {
|
||||
biz.ErrIsNilAppendErr(d.InstanceApp.Delete(rc.MetaCtx, cast.ToUint64(v)), "delete db instance failed: %s")
|
||||
biz.ErrIsNilAppendErr(d.instanceApp.Delete(rc.MetaCtx, cast.ToUint64(v)), "delete db instance failed: %s")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +136,13 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) {
|
||||
func (d *Instance) GetDatabaseNames(rc *req.Ctx) {
|
||||
form := &form.InstanceDbNamesForm{}
|
||||
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
|
||||
res, err := d.InstanceApp.GetDatabases(instance, form.AuthCert)
|
||||
res, err := d.instanceApp.GetDatabases(instance, form.AuthCert)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (d *Instance) GetDatabaseNamesByAc(rc *req.Ctx) {
|
||||
res, err := d.InstanceApp.GetDatabasesByAc(rc.PathParam("ac"))
|
||||
res, err := d.instanceApp.GetDatabasesByAc(rc.PathParam("ac"))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
@@ -120,7 +150,7 @@ func (d *Instance) GetDatabaseNamesByAc(rc *req.Ctx) {
|
||||
// 获取数据库实例server信息
|
||||
func (d *Instance) GetDbServer(rc *req.Ctx) {
|
||||
instanceId := getInstanceId(rc)
|
||||
conn, err := d.DbApp.GetDbConnByInstanceId(instanceId)
|
||||
conn, err := d.dbApp.GetDbConnByInstanceId(instanceId)
|
||||
biz.ErrIsNil(err)
|
||||
res, err := conn.GetMetadata().GetDbServer()
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
@@ -10,7 +10,22 @@ import (
|
||||
)
|
||||
|
||||
type DbSql struct {
|
||||
DbSqlApp application.DbSql `inject:""`
|
||||
dbSqlApp application.DbSql `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *DbSql) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 用户sql相关
|
||||
req.NewPost(":dbId/sql", d.SaveSql),
|
||||
|
||||
req.NewGet(":dbId/sql", d.GetSql),
|
||||
|
||||
req.NewDelete(":dbId/sql", d.DeleteSql),
|
||||
|
||||
req.NewGet(":dbId/sql-names", d.GetSqlNames),
|
||||
}
|
||||
|
||||
return req.NewConfs("/dbs", reqs[:]...)
|
||||
}
|
||||
|
||||
// @router /api/db/:dbId/sql [post]
|
||||
@@ -25,14 +40,14 @@ func (d *DbSql) SaveSql(rc *req.Ctx) {
|
||||
// 获取用于是否有该dbsql的保存记录,有则更改,否则新增
|
||||
dbSql := &entity.DbSql{Type: dbSqlForm.Type, DbId: dbId, Name: dbSqlForm.Name, Db: dbSqlForm.Db}
|
||||
dbSql.CreatorId = account.Id
|
||||
e := d.DbSqlApp.GetByCond(dbSql)
|
||||
e := d.dbSqlApp.GetByCond(dbSql)
|
||||
|
||||
// 更新sql信息
|
||||
dbSql.Sql = dbSqlForm.Sql
|
||||
if e == nil {
|
||||
d.DbSqlApp.UpdateById(rc.MetaCtx, dbSql)
|
||||
d.dbSqlApp.UpdateById(rc.MetaCtx, dbSql)
|
||||
} else {
|
||||
d.DbSqlApp.Insert(rc.MetaCtx, dbSql)
|
||||
d.dbSqlApp.Insert(rc.MetaCtx, dbSql)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +58,7 @@ func (d *DbSql) GetSqlNames(rc *req.Ctx) {
|
||||
// 获取用于是否有该dbsql的保存记录,有则更改,否则新增
|
||||
dbSql := &entity.DbSql{Type: 1, DbId: dbId, Db: dbName}
|
||||
dbSql.CreatorId = rc.GetLoginAccount().Id
|
||||
sqls, _ := d.DbSqlApp.ListByCond(model.NewModelCond(dbSql).Columns("id", "name"))
|
||||
sqls, _ := d.dbSqlApp.ListByCond(model.NewModelCond(dbSql).Columns("id", "name"))
|
||||
|
||||
rc.ResData = sqls
|
||||
}
|
||||
@@ -55,7 +70,7 @@ func (d *DbSql) DeleteSql(rc *req.Ctx) {
|
||||
dbSql.Name = rc.Query("name")
|
||||
dbSql.Db = rc.Query("db")
|
||||
|
||||
biz.ErrIsNil(d.DbSqlApp.DeleteByCond(rc.MetaCtx, dbSql))
|
||||
biz.ErrIsNil(d.dbSqlApp.DeleteByCond(rc.MetaCtx, dbSql))
|
||||
}
|
||||
|
||||
// @router /api/db/:dbId/sql [get]
|
||||
@@ -67,7 +82,7 @@ func (d *DbSql) GetSql(rc *req.Ctx) {
|
||||
dbSql.CreatorId = rc.GetLoginAccount().Id
|
||||
dbSql.Name = rc.Query("name")
|
||||
|
||||
e := d.DbSqlApp.GetByCond(dbSql)
|
||||
e := d.dbSqlApp.GetByCond(dbSql)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,16 @@ import (
|
||||
)
|
||||
|
||||
type DbSqlExec struct {
|
||||
DbSqlExecApp application.DbSqlExec `inject:""`
|
||||
dbSqlExecApp application.DbSqlExec `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *DbSqlExec) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 获取所有数据库sql执行记录列表
|
||||
req.NewGet("/sql-execs", d.DbSqlExecs),
|
||||
}
|
||||
|
||||
return req.NewConfs("/dbs", reqs[:]...)
|
||||
}
|
||||
|
||||
func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) {
|
||||
@@ -24,7 +33,7 @@ func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) {
|
||||
return cast.ToInt8(val)
|
||||
})
|
||||
}
|
||||
res, err := d.DbSqlExecApp.GetPageList(queryCond, page, new([]entity.DbSqlExec))
|
||||
res, err := d.dbSqlExecApp.GetPageList(queryCond, page, new([]entity.DbSqlExec))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
@@ -2,52 +2,73 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/api/form"
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/dbm/sqlparser"
|
||||
"mayfly-go/internal/db/application/dto"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/imsg"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type DbTransferTask struct {
|
||||
DbTransferTask application.DbTransferTask `inject:"DbTransferTaskApp"`
|
||||
DbTransferFile application.DbTransferFile `inject:"DbTransferFileApp"`
|
||||
DbApp application.Db `inject:""`
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
DbSqlExecApp application.DbSqlExec `inject:""`
|
||||
FileApp fileapp.File `inject:""`
|
||||
dbTransferTask application.DbTransferTask `inject:"T"`
|
||||
dbTransferFile application.DbTransferFile `inject:"T"`
|
||||
dbApp application.Db `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
dbSqlExecApp application.DbSqlExec `inject:"T"`
|
||||
fileApp fileapp.File `inject:"T"`
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) ReqConfs() *req.Confs {
|
||||
reqs := [...]*req.Conf{
|
||||
// 获取任务列表
|
||||
req.NewGet("", d.Tasks),
|
||||
|
||||
// 保存任务
|
||||
req.NewPost("save", d.SaveTask).Log(req.NewLogSaveI(imsg.LogDtsSave)).RequiredPermissionCode("db:transfer:save"),
|
||||
|
||||
// 删除任务
|
||||
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSaveI(imsg.LogDtsDelete)).RequiredPermissionCode("db:transfer:del"),
|
||||
|
||||
// 启停用任务
|
||||
req.NewPost(":taskId/status", d.ChangeStatus).Log(req.NewLogSaveI(imsg.LogDtsChangeStatus)).RequiredPermissionCode("db:transfer:status"),
|
||||
|
||||
// 立即执行任务
|
||||
req.NewPost(":taskId/run", d.Run).Log(req.NewLogI(imsg.LogDtsRun)).RequiredPermissionCode("db:transfer:run"),
|
||||
|
||||
// 停止正在执行中的任务
|
||||
req.NewPost(":taskId/stop", d.Stop).Log(req.NewLogSaveI(imsg.LogDtsStop)).RequiredPermissionCode("db:transfer:run"),
|
||||
|
||||
// 导出文件管理-列表
|
||||
req.NewGet("/files/:taskId", d.Files),
|
||||
|
||||
// 导出文件管理-删除
|
||||
req.NewPost("/files/del/:fileId", d.FileDel).Log(req.NewLogSaveI(imsg.LogDtsDeleteFile)).RequiredPermissionCode("db:transfer:files:del"),
|
||||
|
||||
req.NewPost("/files/run", d.FileRun).Log(req.NewLogSaveI(imsg.LogDtsRunSqlFile)).RequiredPermissionCode("db:transfer:files:run"),
|
||||
}
|
||||
|
||||
return req.NewConfs("/dbTransfer", reqs[:]...)
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbTransferTaskQuery](rc, new(entity.DbTransferTaskQuery))
|
||||
res, err := d.DbTransferTask.GetPageList(queryCond, page, new([]vo.DbTransferTaskListVO))
|
||||
res, err := d.dbTransferTask.GetPageList(queryCond, page, new([]vo.DbTransferTaskListVO))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
if res.List != nil {
|
||||
list := res.List.(*[]vo.DbTransferTaskListVO)
|
||||
for _, item := range *list {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.DbTransferTask.IsRunning(item.Id) {
|
||||
if d.dbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
}
|
||||
}
|
||||
@@ -61,7 +82,7 @@ func (d *DbTransferTask) SaveTask(rc *req.Ctx) {
|
||||
task := req.BindJsonAndCopyTo[*entity.DbTransferTask](rc, reqForm, new(entity.DbTransferTask))
|
||||
|
||||
rc.ReqParam = reqForm
|
||||
biz.ErrIsNil(d.DbTransferTask.Save(rc.MetaCtx, task))
|
||||
biz.ErrIsNil(d.dbTransferTask.Save(rc.MetaCtx, task))
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) DeleteTask(rc *req.Ctx) {
|
||||
@@ -73,17 +94,17 @@ func (d *DbTransferTask) DeleteTask(rc *req.Ctx) {
|
||||
return cast.ToUint64(val)
|
||||
})
|
||||
|
||||
biz.ErrIsNil(d.DbTransferTask.DeleteById(rc.MetaCtx, uids...))
|
||||
biz.ErrIsNil(d.dbTransferTask.DeleteById(rc.MetaCtx, uids...))
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) ChangeStatus(rc *req.Ctx) {
|
||||
form := &form.DbTransferTaskStatusForm{}
|
||||
task := req.BindJsonAndCopyTo[*entity.DbTransferTask](rc, form, new(entity.DbTransferTask))
|
||||
_ = d.DbTransferTask.UpdateById(rc.MetaCtx, task)
|
||||
_ = d.dbTransferTask.UpdateById(rc.MetaCtx, task)
|
||||
|
||||
task, err := d.DbTransferTask.GetById(task.Id)
|
||||
task, err := d.dbTransferTask.GetById(task.Id)
|
||||
biz.ErrIsNil(err, "task not found")
|
||||
d.DbTransferTask.AddCronJob(rc.MetaCtx, task)
|
||||
d.dbTransferTask.AddCronJob(rc.MetaCtx, task)
|
||||
|
||||
// 记录请求日志
|
||||
rc.ReqParam = form
|
||||
@@ -91,18 +112,18 @@ func (d *DbTransferTask) ChangeStatus(rc *req.Ctx) {
|
||||
|
||||
func (d *DbTransferTask) Run(rc *req.Ctx) {
|
||||
taskId := uint64(rc.PathParamInt("taskId"))
|
||||
logId, _ := d.DbTransferTask.CreateLog(rc.MetaCtx, taskId)
|
||||
go d.DbTransferTask.Run(rc.MetaCtx, taskId, logId)
|
||||
logId, _ := d.dbTransferTask.CreateLog(rc.MetaCtx, taskId)
|
||||
go d.dbTransferTask.Run(rc.MetaCtx, taskId, logId)
|
||||
rc.ResData = logId
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Stop(rc *req.Ctx) {
|
||||
biz.ErrIsNil(d.DbTransferTask.Stop(rc.MetaCtx, uint64(rc.PathParamInt("taskId"))))
|
||||
biz.ErrIsNil(d.dbTransferTask.Stop(rc.MetaCtx, uint64(rc.PathParamInt("taskId"))))
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Files(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbTransferFileQuery](rc, new(entity.DbTransferFileQuery))
|
||||
res, err := d.DbTransferFile.GetPageList(queryCond, page, new([]vo.DbTransferFileListVO))
|
||||
res, err := d.dbTransferFile.GetPageList(queryCond, page, new([]vo.DbTransferFileListVO))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
@@ -116,71 +137,29 @@ func (d *DbTransferTask) FileDel(rc *req.Ctx) {
|
||||
for _, v := range ids {
|
||||
uIds = append(uIds, cast.ToUint64(v))
|
||||
}
|
||||
biz.ErrIsNil(d.DbTransferFile.Delete(rc.MetaCtx, uIds...))
|
||||
biz.ErrIsNil(d.dbTransferFile.Delete(rc.MetaCtx, uIds...))
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
|
||||
fm := req.BindJsonAndValid(rc, &form.DbTransferFileRunForm{})
|
||||
|
||||
rc.ReqParam = fm
|
||||
|
||||
tFile, err := d.DbTransferFile.GetById(fm.Id)
|
||||
tFile, err := d.dbTransferFile.GetById(fm.Id)
|
||||
biz.IsTrue(tFile != nil && err == nil, "file not found")
|
||||
|
||||
targetDbConn, err := d.DbApp.GetDbConn(fm.TargetDbId, fm.TargetDbName)
|
||||
targetDbConn, err := d.dbApp.GetDbConn(fm.TargetDbId, fm.TargetDbName)
|
||||
biz.ErrIsNilAppendErr(err, "failed to connect to the target database: %s")
|
||||
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, targetDbConn.Info.CodePath...), "%s")
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errInfo := anyx.ToString(err)
|
||||
if len(errInfo) > 300 {
|
||||
errInfo = errInfo[:300] + "..."
|
||||
}
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] run failed: [%s]", tFile.FileKey, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
|
||||
}
|
||||
}()
|
||||
biz.ErrIsNilAppendErr(d.tagApp.CanAccess(rc.GetLoginAccount().Id, targetDbConn.Info.CodePath...), "%s")
|
||||
|
||||
filename, reader, err := d.fileApp.GetReader(context.TODO(), tFile.FileKey)
|
||||
biz.ErrIsNil(err)
|
||||
go func() {
|
||||
d.fileRun(rc.GetLoginAccount(), fm, tFile, targetDbConn)
|
||||
biz.ErrIsNil(d.dbSqlExecApp.ExecReader(rc.MetaCtx, &dto.SqlReaderExec{
|
||||
Reader: reader,
|
||||
Filename: filename,
|
||||
DbConn: targetDbConn,
|
||||
ClientId: fm.ClientId,
|
||||
}))
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFileRunForm, tFile *entity.DbTransferFile, targetDbConn *dbi.DbConn) {
|
||||
filename, reader, err := d.FileApp.GetReader(context.TODO(), tFile.FileKey)
|
||||
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
laId := la.Id
|
||||
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
|
||||
if err != nil {
|
||||
biz.ErrIsNilAppendErr(err, "failed to connect to the target database: %s")
|
||||
}
|
||||
|
||||
err = sqlparser.SQLSplit(reader, func(sql string) error {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ws.SendJsonMsg(ws.UserId(laId), fm.ClientId, msgdto.InfoSqlProgressMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: false,
|
||||
}).WithCategory(progressCategory))
|
||||
default:
|
||||
}
|
||||
executedStatements++
|
||||
_, err = targetDbConn.Exec(sql)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
biz.ErrIsNilAppendErr(err, "sql execution failed: %s")
|
||||
}
|
||||
|
||||
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), fmt.Sprintf("sql execution successfully: %s", filename)).WithClientId(fm.ClientId))
|
||||
}
|
||||
|
||||
@@ -52,20 +52,15 @@ type Db interface {
|
||||
type dbAppImpl struct {
|
||||
base.AppImpl[*entity.Db, repository.Db]
|
||||
|
||||
dbSqlRepo repository.DbSql `inject:"DbSqlRepo"`
|
||||
dbInstanceApp Instance `inject:"DbInstanceApp"`
|
||||
dbSqlExecApp DbSqlExec `inject:"DbSqlExecApp"`
|
||||
tagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
|
||||
dbSqlRepo repository.DbSql `inject:"T"`
|
||||
dbInstanceApp Instance `inject:"T"`
|
||||
dbSqlExecApp DbSqlExec `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"T"`
|
||||
}
|
||||
|
||||
var _ (Db) = (*dbAppImpl)(nil)
|
||||
|
||||
// 注入DbRepo
|
||||
func (d *dbAppImpl) InjectDbRepo(repo repository.Db) {
|
||||
d.Repo = repo
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return d.GetRepo().GetDbList(condition, pageParam, toEntity, orderBy...)
|
||||
@@ -94,11 +89,11 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
|
||||
return d.tagApp.RelateTagsByCodeAndType(ctx, &tagdto.RelateTagsByCodeAndType{
|
||||
Tags: []*tagdto.ResourceTag{{
|
||||
Code: dbEntity.Code,
|
||||
Type: tagentity.TagTypeDbName,
|
||||
Type: tagentity.TagTypeDb,
|
||||
Name: dbEntity.Name,
|
||||
}},
|
||||
ParentTagCode: authCert.Name,
|
||||
ParentTagType: tagentity.TagTypeDbAuthCert,
|
||||
ParentTagType: tagentity.TagTypeAuthCert,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -136,12 +131,12 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
|
||||
return d.UpdateById(ctx, dbEntity)
|
||||
}, func(ctx context.Context) error {
|
||||
if old.Name != dbEntity.Name {
|
||||
if err := d.tagApp.UpdateTagName(ctx, tagentity.TagTypeDbName, old.Code, dbEntity.Name); err != nil {
|
||||
if err := d.tagApp.UpdateTagName(ctx, tagentity.TagTypeDb, old.Code, dbEntity.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if authCert.Name != old.AuthCertName {
|
||||
return d.tagApp.ChangeParentTag(ctx, tagentity.TagTypeDbName, old.Code, tagentity.TagTypeDbAuthCert, authCert.Name)
|
||||
return d.tagApp.ChangeParentTag(ctx, tagentity.TagTypeDb, old.Code, tagentity.TagTypeAuthCert, authCert.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -170,7 +165,7 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
}, func(ctx context.Context) error {
|
||||
return d.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
|
||||
ResourceCode: db.Code,
|
||||
ResourceType: tagentity.TagTypeDbName,
|
||||
ResourceType: tagentity.TagTypeDb,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -191,7 +186,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
di.CodePath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDbName), db.Code)
|
||||
di.CodePath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDb), db.Code)
|
||||
di.Id = db.Id
|
||||
|
||||
checkDb := di.GetDatabase()
|
||||
@@ -214,7 +209,7 @@ func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbi.DbConn, error
|
||||
return nil, errorx.NewBiz("failed to get database list")
|
||||
}
|
||||
if len(dbs) == 0 {
|
||||
return nil, errorx.NewBiz("DB instance [%d] Database is not configured, please configure it first", instanceId)
|
||||
return nil, errorx.NewBiz("DB instance [%d] database is not configured, please configure it first", instanceId)
|
||||
}
|
||||
|
||||
// 使用该实例关联的已配置数据库中的第一个库进行连接并返回
|
||||
@@ -227,6 +222,10 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
if reqParam.Log != nil {
|
||||
log = reqParam.Log
|
||||
}
|
||||
progress := dto.DefaultDumpProgress
|
||||
if reqParam.Progress != nil {
|
||||
progress = reqParam.Progress
|
||||
}
|
||||
|
||||
writer := writerx.NewStringWriter(reqParam.Writer)
|
||||
defer writer.Close()
|
||||
@@ -249,23 +248,16 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
// 获取目标元数据,仅生成sql,用于生成建表语句和插入数据,不能用于查询
|
||||
targetDialect := dbConn.GetDialect()
|
||||
if reqParam.TargetDbType != "" && dbConn.Info.Type != reqParam.TargetDbType {
|
||||
// 创建一个假连接,仅用于调用方言生成sql,不做数据库连接操作
|
||||
meta := dbi.GetMeta(reqParam.TargetDbType)
|
||||
dbConn := &dbi.DbConn{Info: &dbi.DbInfo{
|
||||
Type: reqParam.TargetDbType,
|
||||
Meta: meta,
|
||||
}}
|
||||
|
||||
targetDialect = meta.GetDialect(dbConn)
|
||||
targetDialect = dbi.GetDialect(reqParam.TargetDbType)
|
||||
}
|
||||
|
||||
srcMeta := dbConn.GetMetadata()
|
||||
srcDialect := dbConn.GetDialect()
|
||||
if len(tables) == 0 {
|
||||
log("Gets the table information that can be export...")
|
||||
log("gets the table information that can be export...")
|
||||
ti, err := srcMeta.GetTables()
|
||||
if err != nil {
|
||||
log(fmt.Sprintf("Failed to get table info %s", err.Error()))
|
||||
log(fmt.Sprintf("failed to get table info %s", err.Error()))
|
||||
}
|
||||
biz.ErrIsNil(err)
|
||||
tables = make([]string, len(ti))
|
||||
@@ -275,105 +267,133 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
log(fmt.Sprintf("Get %d tables", len(tables)))
|
||||
}
|
||||
if len(tables) == 0 {
|
||||
log("No table to export. End export")
|
||||
log("no table to export. end export")
|
||||
return errorx.NewBiz("there is no table to export")
|
||||
}
|
||||
|
||||
log("Querying column information...")
|
||||
log("querying column information...")
|
||||
// 查询列信息,后面生成建表ddl和insert都需要列信息
|
||||
columns, err := srcMeta.GetColumns(tables...)
|
||||
if err != nil {
|
||||
log(fmt.Sprintf("Failed to query column information: %s", err.Error()))
|
||||
log(fmt.Sprintf("failed to query column information: %s", err.Error()))
|
||||
}
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
// 以表名分组,存放每个表的列信息
|
||||
columnMap := make(map[string][]dbi.Column)
|
||||
for _, column := range columns {
|
||||
if err := dbi.ConvToTargetDbColumn(dbConn.Info.Type, cmp.Or(reqParam.TargetDbType, dbConn.Info.Type), targetDialect, &column); err != nil {
|
||||
return err
|
||||
}
|
||||
columnMap[column.TableName] = append(columnMap[column.TableName], column)
|
||||
}
|
||||
|
||||
// 按表名排序
|
||||
sort.Strings(tables)
|
||||
|
||||
quoteSchema := srcDialect.QuoteIdentifier(dbConn.Info.CurrentSchema())
|
||||
quoteSchema := srcDialect.Quoter().Quote(dbConn.Info.CurrentSchema())
|
||||
dumpHelper := targetDialect.GetDumpHelper()
|
||||
dataHelper := targetDialect.GetDataHelper()
|
||||
|
||||
targetSqlGenerator := targetDialect.GetSQLGenerator()
|
||||
targetDialectQuote := targetDialect.Quoter().Quote
|
||||
// 遍历获取每个表的信息
|
||||
for _, tableName := range tables {
|
||||
log(fmt.Sprintf("Get table [%s] information...", tableName))
|
||||
quoteTableName := targetDialect.QuoteIdentifier(tableName)
|
||||
log(fmt.Sprintf("get table [%s] information...", tableName))
|
||||
quoteTableName := targetDialectQuote(tableName)
|
||||
|
||||
// 查询表信息,主要是为了查询表注释
|
||||
tbs, err := srcMeta.GetTables(tableName)
|
||||
if err != nil {
|
||||
log(fmt.Sprintf("Failed to get table [%s] information: %s", tableName, err.Error()))
|
||||
log(fmt.Sprintf("failed to get table [%s] information: %s", tableName, err.Error()))
|
||||
return err
|
||||
}
|
||||
if len(tbs) <= 0 {
|
||||
log(fmt.Sprintf("Failed to get table [%s] information: No table information was retrieved", tableName))
|
||||
return errorx.NewBiz(fmt.Sprintf("Failed to get table information: %s", tableName))
|
||||
}
|
||||
tabInfo := dbi.Table{
|
||||
TableName: tableName,
|
||||
TableComment: tbs[0].TableComment,
|
||||
log(fmt.Sprintf("failed to get table [%s] information: No table information was retrieved", tableName))
|
||||
return errorx.NewBiz("Failed to get table information: %s", tableName)
|
||||
}
|
||||
|
||||
tableInfo := tbs[0]
|
||||
columns := columnMap[tableName]
|
||||
|
||||
// 生成表结构信息
|
||||
if reqParam.DumpDDL {
|
||||
log(fmt.Sprintf("Generate table [%s] DDL...", tableName))
|
||||
log(fmt.Sprintf("generate table [%s] DDL...", tableName))
|
||||
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- Table structure: %s \n-- ----------------------------\n", tableName))
|
||||
tbDdlArr := targetDialect.GenerateTableDDL(columnMap[tableName], tabInfo, true)
|
||||
tbDdlArr := targetSqlGenerator.GenTableDDL(tableInfo, columns, true)
|
||||
for _, ddl := range tbDdlArr {
|
||||
writer.WriteString(ddl + ";\n")
|
||||
if _, err := writer.WriteString(ddl + ";\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
progress(tableName, dbi.StmtTypeDDL, len(tbDdlArr), true)
|
||||
}
|
||||
|
||||
// 生成insert sql,数据在索引前,加速insert
|
||||
if reqParam.DumpData {
|
||||
log(fmt.Sprintf("Generate table [%s] DML...", tableName))
|
||||
log(fmt.Sprintf("generate table [%s] DML...", tableName))
|
||||
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- Data: %s \n-- ----------------------------\n", tableName))
|
||||
|
||||
dumpHelper.BeforeInsert(writer, quoteTableName)
|
||||
// 获取列信息
|
||||
quoteColNames := make([]string, 0)
|
||||
for _, col := range columnMap[tableName] {
|
||||
quoteColNames = append(quoteColNames, targetDialect.QuoteIdentifier(col.ColumnName))
|
||||
}
|
||||
|
||||
_, _ = dbConn.WalkTableRows(ctx, tableName, func(row map[string]any, _ []*dbi.QueryColumn) error {
|
||||
rowValues := make([]string, len(columnMap[tableName]))
|
||||
for i, col := range columnMap[tableName] {
|
||||
rowValues[i] = dataHelper.WrapValue(row[col.ColumnName], dataHelper.GetDataType(string(col.DataType)))
|
||||
dataCount := 0
|
||||
rows := make([][]any, 0)
|
||||
_, err = dbConn.WalkTableRows(ctx, tableName, func(row map[string]any, _ []*dbi.QueryColumn) error {
|
||||
rowValues := make([]any, len(columns))
|
||||
for i, col := range columns {
|
||||
rowValues[i] = row[col.ColumnName]
|
||||
}
|
||||
rows = append(rows, rowValues)
|
||||
dataCount++
|
||||
if dataCount%500 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
beforeInsert := dumpHelper.BeforeInsertSql(quoteSchema, quoteTableName)
|
||||
insertSQL := fmt.Sprintf("%s INSERT INTO %s (%s) values(%s)", beforeInsert, quoteTableName, strings.Join(quoteColNames, ", "), strings.Join(rowValues, ", "))
|
||||
writer.WriteString(insertSQL + ";\n")
|
||||
writer.WriteString(beforeInsert)
|
||||
insertSql := targetSqlGenerator.GenInsert(tableName, columns, rows, dbi.DuplicateStrategyNone)
|
||||
if _, err := writer.WriteString(strings.Join(insertSql, ";\n") + ";\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
progress(tableName, dbi.StmtTypeInsert, dataCount, false)
|
||||
rows = make([][]any, 0)
|
||||
return nil
|
||||
})
|
||||
|
||||
dumpHelper.AfterInsert(writer, tableName, columnMap[tableName])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rows) > 0 {
|
||||
beforeInsert := dumpHelper.BeforeInsertSql(quoteSchema, quoteTableName)
|
||||
writer.WriteString(beforeInsert)
|
||||
insertSql := targetSqlGenerator.GenInsert(tableName, columns, rows, dbi.DuplicateStrategyNone)
|
||||
if _, err := writer.WriteString(strings.Join(insertSql, ";\n") + ";\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dumpHelper.AfterInsert(writer, tableName, columns)
|
||||
progress(tableName, dbi.StmtTypeInsert, dataCount, true)
|
||||
}
|
||||
|
||||
log(fmt.Sprintf("Get table [%s] index information...", tableName))
|
||||
log(fmt.Sprintf("get table [%s] index information...", tableName))
|
||||
indexs, err := srcMeta.GetTableIndex(tableName)
|
||||
if err != nil {
|
||||
log(fmt.Sprintf("Failed to get table [%s] index information: %s", tableName, err.Error()))
|
||||
log(fmt.Sprintf("failed to get table [%s] index information: %s", tableName, err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(indexs) > 0 {
|
||||
// 最后添加索引
|
||||
log(fmt.Sprintf("Generate table [%s] index...", tableName))
|
||||
log(fmt.Sprintf("generate table [%s] index...", tableName))
|
||||
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- Table Index: %s \n-- ----------------------------\n", tableName))
|
||||
sqlArr := targetDialect.GenerateIndexDDL(indexs, tabInfo)
|
||||
sqlArr := targetSqlGenerator.GenIndexDDL(tableInfo, indexs)
|
||||
for _, sqlStr := range sqlArr {
|
||||
writer.WriteString(sqlStr + ";\n")
|
||||
if _, err := writer.WriteString(sqlStr + ";\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
progress(tableName, dbi.StmtTypeDDL, len(sqlArr), true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
@@ -14,8 +15,8 @@ import (
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/scheduler"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -46,15 +47,13 @@ type DataSyncTask interface {
|
||||
type dataSyncAppImpl struct {
|
||||
base.AppImpl[*entity.DataSyncTask, repository.DataSyncTask]
|
||||
|
||||
dbDataSyncLogRepo repository.DataSyncLog `inject:"DbDataSyncLogRepo"`
|
||||
dbDataSyncLogRepo repository.DataSyncLog `inject:"T"`
|
||||
|
||||
dbApp Db `inject:"DbApp"`
|
||||
dbApp Db `inject:"T"`
|
||||
}
|
||||
|
||||
var (
|
||||
dateTimeReg = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`)
|
||||
dateTimeIsoReg = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*$`)
|
||||
whereReg = regexp.MustCompile(`(?i)where`)
|
||||
whereReg = regexp.MustCompile(`(?i)where`)
|
||||
)
|
||||
|
||||
func (app *dataSyncAppImpl) InjectDbDataSyncTaskRepo(repo repository.DataSyncTask) {
|
||||
@@ -145,28 +144,28 @@ func (app *dataSyncAppImpl) RunCronJob(ctx context.Context, id uint64) error {
|
||||
updSql := ""
|
||||
orderSql := ""
|
||||
if task.UpdFieldVal != "0" && task.UpdFieldVal != "" && task.UpdField != "" {
|
||||
srcConn, err := app.dbApp.GetDbConn(uint64(task.SrcDbId), task.SrcDbName)
|
||||
if err != nil {
|
||||
logx.ErrorfContext(ctx, "data source connection unavailable: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
task.UpdFieldVal = strings.Trim(task.UpdFieldVal, " ")
|
||||
|
||||
// 判断UpdFieldVal数据类型
|
||||
var updFieldValType dbi.DataType
|
||||
if _, err = strconv.Atoi(task.UpdFieldVal); err != nil {
|
||||
if dateTimeReg.MatchString(task.UpdFieldVal) || dateTimeIsoReg.MatchString(task.UpdFieldVal) {
|
||||
updFieldValType = dbi.DataTypeDateTime
|
||||
} else {
|
||||
updFieldValType = dbi.DataTypeString
|
||||
}
|
||||
} else {
|
||||
updFieldValType = dbi.DataTypeNumber
|
||||
srcConn, err := app.dbApp.GetDbConn(uint64(task.SrcDbId), task.SrcDbName)
|
||||
if err != nil {
|
||||
logx.ErrorfContext(ctx, "failed to connect to the source database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
wrapUpdFieldVal := srcConn.GetDialect().GetDataHelper().WrapValue(task.UpdFieldVal, updFieldValType)
|
||||
updSql = fmt.Sprintf("and %s > %s", task.UpdField, wrapUpdFieldVal)
|
||||
|
||||
updFieldDataType := dbi.DefaultDbDataType
|
||||
srcConn.WalkQueryRows(context.Background(), task.DataSql, func(row map[string]any, columns []*dbi.QueryColumn) error {
|
||||
for _, column := range columns {
|
||||
if strings.EqualFold(column.Name, cmp.Or(task.UpdFieldSrc, task.UpdField)) {
|
||||
updFieldDataType = column.DbDataType
|
||||
break
|
||||
}
|
||||
}
|
||||
return errorx.NewBiz("get column data type")
|
||||
})
|
||||
|
||||
updSql = fmt.Sprintf("and %s > %s", task.UpdField, updFieldDataType.DataType.SQLValue(task.UpdFieldVal))
|
||||
orderSql = "order by " + task.UpdField + " asc "
|
||||
}
|
||||
// 正则判断DataSql是否以where .*结尾,如果是则不添加where 1 = 1
|
||||
@@ -176,9 +175,9 @@ func (app *dataSyncAppImpl) RunCronJob(ctx context.Context, id uint64) error {
|
||||
}
|
||||
|
||||
// 组装查询sql
|
||||
sql := fmt.Sprintf("%s %s %s %s", task.DataSql, where, updSql, orderSql)
|
||||
sqlStr := fmt.Sprintf("%s %s %s %s", task.DataSql, where, updSql, orderSql)
|
||||
|
||||
log, err := app.doDataSync(ctx, sql, task)
|
||||
log, err := app.doDataSync(ctx, sqlStr, task)
|
||||
if err != nil {
|
||||
log.ErrText = fmt.Sprintf("execution failure: %s", err.Error())
|
||||
logx.ErrorContext(ctx, log.ErrText)
|
||||
@@ -221,15 +220,13 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
|
||||
}
|
||||
}()
|
||||
|
||||
srcDialect := srcConn.GetDialect()
|
||||
|
||||
// task.FieldMap为json数组字符串 [{"src":"id","target":"id"}],转为map
|
||||
var fieldMap []map[string]string
|
||||
err = json.Unmarshal([]byte(task.FieldMap), &fieldMap)
|
||||
if err != nil {
|
||||
return syncLog, errorx.NewBiz("there was an error parsing the field map json: %s", err.Error())
|
||||
}
|
||||
var updFieldType dbi.DataType
|
||||
var updFieldType *dbi.DbDataType
|
||||
|
||||
// 记录本次同步数据总数
|
||||
total := 0
|
||||
@@ -243,15 +240,28 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
|
||||
updFieldName = strings.Split(task.UpdField, ".")[1]
|
||||
}
|
||||
|
||||
targetTableColumns, err := targetConn.GetMetadata().GetColumns(task.TargetTableName)
|
||||
if err != nil {
|
||||
return syncLog, errorx.NewBiz("failed to get target table columns: %s", err.Error())
|
||||
}
|
||||
targetColumnName2Column := collx.ArrayToMap(targetTableColumns, func(column dbi.Column) string {
|
||||
return column.ColumnName
|
||||
})
|
||||
|
||||
// 目标库对应的insert columns
|
||||
targetInsertColumns := collx.ArrayMap[map[string]string, dbi.Column](fieldMap, func(val map[string]string) dbi.Column {
|
||||
return targetColumnName2Column[val["target"]]
|
||||
})
|
||||
|
||||
_, err = srcConn.WalkQueryRows(context.Background(), sql, func(row map[string]any, columns []*dbi.QueryColumn) error {
|
||||
if len(queryColumns) == 0 {
|
||||
queryColumns = columns
|
||||
|
||||
// 遍历columns 取task.UpdField的字段类型
|
||||
updFieldType = dbi.DataTypeString
|
||||
updFieldType = dbi.DefaultDbDataType
|
||||
for _, column := range columns {
|
||||
if strings.EqualFold(column.Name, updFieldName) {
|
||||
updFieldType = srcDialect.GetDataHelper().GetDataType(column.Type)
|
||||
updFieldType = column.DbDataType
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -260,7 +270,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
|
||||
total++
|
||||
result = append(result, row)
|
||||
if total%batchSize == 0 {
|
||||
if err := app.srcData2TargetDb(result, fieldMap, columns, updFieldType, updFieldName, task, srcDialect, targetConn, targetDbTx); err != nil {
|
||||
if err := app.srcData2TargetDb(result, fieldMap, updFieldType, updFieldName, task, targetConn, targetDbTx, targetInsertColumns); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -283,7 +293,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
|
||||
|
||||
// 处理剩余的数据
|
||||
if len(result) > 0 {
|
||||
if err := app.srcData2TargetDb(result, fieldMap, queryColumns, updFieldType, updFieldName, task, srcDialect, targetConn, targetDbTx); err != nil {
|
||||
if err := app.srcData2TargetDb(result, fieldMap, updFieldType, updFieldName, task, targetConn, targetDbTx, targetInsertColumns); err != nil {
|
||||
targetDbTx.Rollback()
|
||||
return syncLog, err
|
||||
}
|
||||
@@ -291,7 +301,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
|
||||
|
||||
// 如果是mssql,暂不手动提交事务,否则报错 mssql: The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
|
||||
if err := targetDbTx.Commit(); err != nil {
|
||||
if targetConn.Info.Type != dbi.DbTypeMssql {
|
||||
if targetConn.Info.Type != dbi.ToDbType("mssql") {
|
||||
return syncLog, errorx.NewBiz("data synchronization - The target database transaction failed to commit: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -307,75 +317,52 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
|
||||
return syncLog, nil
|
||||
}
|
||||
|
||||
func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap []map[string]string, columns []*dbi.QueryColumn, updFieldType dbi.DataType, updFieldName string, task *entity.DataSyncTask, srcDialect dbi.Dialect, targetDbConn *dbi.DbConn, targetDbTx *sql.Tx) error {
|
||||
|
||||
// 遍历src字段列表,取出字段对应的类型
|
||||
var srcColumnTypes = make(map[string]string)
|
||||
for _, column := range columns {
|
||||
srcColumnTypes[column.Name] = column.Type
|
||||
}
|
||||
|
||||
func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap []map[string]string, updFieldType *dbi.DbDataType, updFieldName string, task *entity.DataSyncTask, targetDbConn *dbi.DbConn, targetDbTx *sql.Tx, targetInsertColumns []dbi.Column) error {
|
||||
// 遍历res,组装数据
|
||||
var data = make([]map[string]any, 0)
|
||||
for _, record := range srcRes {
|
||||
var rowData = make(map[string]any)
|
||||
var targetData = make([]map[string]any, 0)
|
||||
for _, srcData := range srcRes {
|
||||
var data = make(map[string]any)
|
||||
// 遍历字段映射, target字段的值为src字段取值
|
||||
for _, item := range fieldMap {
|
||||
srcField := item["src"]
|
||||
targetField := item["target"]
|
||||
// target字段的值为src字段取值
|
||||
rowData[targetField] = record[srcField]
|
||||
data[item["target"]] = srcData[item["src"]]
|
||||
}
|
||||
|
||||
data = append(data, rowData)
|
||||
targetData = append(targetData, data)
|
||||
}
|
||||
|
||||
tragetValues := make([][]any, 0)
|
||||
for _, item := range targetData {
|
||||
var values = make([]any, 0)
|
||||
for _, column := range targetInsertColumns {
|
||||
values = append(values, item[column.ColumnName])
|
||||
}
|
||||
tragetValues = append(tragetValues, values)
|
||||
}
|
||||
|
||||
// 执行插入
|
||||
|
||||
setUpdateFieldVal := func(field string) {
|
||||
// 解决字段大小写问题
|
||||
updFieldVal := srcRes[len(srcRes)-1][strings.ToUpper(field)]
|
||||
if updFieldVal == "" || updFieldVal == nil {
|
||||
updFieldVal = srcRes[len(srcRes)-1][strings.ToLower(field)]
|
||||
}
|
||||
task.UpdFieldVal = srcDialect.GetDataHelper().FormatData(updFieldVal, updFieldType)
|
||||
|
||||
task.UpdFieldVal = updFieldType.DataType.SQLValue(updFieldVal)
|
||||
}
|
||||
|
||||
// 如果指定了更新字段,则以更新字段取值
|
||||
if task.UpdFieldSrc != "" {
|
||||
setUpdateFieldVal(task.UpdFieldSrc)
|
||||
} else {
|
||||
setUpdateFieldVal(updFieldName)
|
||||
}
|
||||
setUpdateFieldVal(cmp.Or(task.UpdFieldSrc, updFieldName))
|
||||
|
||||
// 获取目标库字段数组
|
||||
targetWrapColumns := make([]string, 0)
|
||||
// 获取源库字段数组
|
||||
srcColumns := make([]string, 0)
|
||||
srcFieldTypes := make(map[string]dbi.DataType)
|
||||
targetDialect := targetDbConn.GetDialect()
|
||||
for _, item := range fieldMap {
|
||||
targetField := item["target"]
|
||||
srcField := item["target"]
|
||||
srcFieldTypes[srcField] = srcDialect.GetDataHelper().GetDataType(srcColumnTypes[item["src"]])
|
||||
targetWrapColumns = append(targetWrapColumns, targetDialect.QuoteIdentifier(targetField))
|
||||
srcColumns = append(srcColumns, srcField)
|
||||
}
|
||||
|
||||
// 从目标库数据中取出源库字段对应的值
|
||||
values := make([][]any, 0)
|
||||
for _, record := range data {
|
||||
rawValue := make([]any, 0)
|
||||
for _, column := range srcColumns {
|
||||
// 某些情况,如oracle,需要转换时间类型的字符串为time类型
|
||||
res := srcDialect.GetDataHelper().ParseData(record[column], srcFieldTypes[column])
|
||||
rawValue = append(rawValue, res)
|
||||
// 生成目标数据库批量插入sql,并执行
|
||||
sqls := targetDialect.GetSQLGenerator().GenInsert(task.TargetTableName, targetInsertColumns, tragetValues, cmp.Or(task.DuplicateStrategy, dbi.DuplicateStrategyNone))
|
||||
for _, sql := range sqls {
|
||||
_, err := targetDbTx.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values = append(values, rawValue)
|
||||
}
|
||||
|
||||
// 目标数据库执行sql批量插入
|
||||
_, err := targetDialect.BatchInsert(targetDbTx, task.TargetTableName, targetWrapColumns, values, task.DuplicateStrategy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 运行过程中,判断状态是否为已关闭,是则结束运行,否则继续运行
|
||||
|
||||
@@ -47,18 +47,13 @@ type Instance interface {
|
||||
type instanceAppImpl struct {
|
||||
base.AppImpl[*entity.DbInstance, repository.Instance]
|
||||
|
||||
tagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
|
||||
dbApp Db `inject:"DbApp"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"T"`
|
||||
dbApp Db `inject:"T"`
|
||||
}
|
||||
|
||||
var _ (Instance) = (*instanceAppImpl)(nil)
|
||||
|
||||
// 注入DbInstanceRepo
|
||||
func (app *instanceAppImpl) InjectDbInstanceRepo(repo repository.Instance) {
|
||||
app.Repo = repo
|
||||
}
|
||||
|
||||
// GetPageList 分页获取数据库实例
|
||||
func (app *instanceAppImpl) GetPageList(condition *entity.InstanceQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.GetRepo().GetInstanceList(condition, pageParam, toEntity, orderBy...)
|
||||
@@ -84,7 +79,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
|
||||
instanceEntity := instance.DbInstance
|
||||
// 默认tcp连接
|
||||
instanceEntity.Network = instanceEntity.GetNetwork()
|
||||
resourceType := consts.ResourceTypeDb
|
||||
resourceType := consts.ResourceTypeDbInstance
|
||||
authCerts := instance.AuthCerts
|
||||
tagCodePaths := instance.TagCodePaths
|
||||
|
||||
@@ -145,7 +140,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
|
||||
})
|
||||
}, func(ctx context.Context) error {
|
||||
if instanceEntity.Name != oldInstance.Name {
|
||||
if err := app.tagApp.UpdateTagName(ctx, tagentity.TagTypeDb, oldInstance.Code, instanceEntity.Name); err != nil {
|
||||
if err := app.tagApp.UpdateTagName(ctx, tagentity.TagTypeDbInstance, oldInstance.Code, instanceEntity.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -172,12 +167,12 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
|
||||
// 删除该实例关联的授权凭证信息
|
||||
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
|
||||
ResourceCode: instance.Code,
|
||||
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
|
||||
ResourceType: tagentity.TagType(consts.ResourceTypeDbInstance),
|
||||
})
|
||||
}, func(ctx context.Context) error {
|
||||
return app.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
|
||||
ResourceCode: instance.Code,
|
||||
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
|
||||
ResourceType: tagentity.TagType(consts.ResourceTypeDbInstance),
|
||||
})
|
||||
}, func(ctx context.Context) error {
|
||||
// 删除所有库配置
|
||||
@@ -260,7 +255,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
|
||||
return &tagdto.ResourceTag{
|
||||
Code: val.Name,
|
||||
Name: val.Username,
|
||||
Type: tagentity.TagTypeDbAuthCert,
|
||||
Type: tagentity.TagTypeAuthCert,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -276,7 +271,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
|
||||
authCertName2DbTags[db.AuthCertName] = append(authCertName2DbTags[db.AuthCertName], &tagdto.ResourceTag{
|
||||
Code: db.Code,
|
||||
Name: db.Name,
|
||||
Type: tagentity.TagTypeDbName,
|
||||
Type: tagentity.TagTypeDb,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -287,7 +282,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
|
||||
|
||||
return &tagdto.ResourceTag{
|
||||
Code: me.Code,
|
||||
Type: tagentity.TagTypeDb,
|
||||
Type: tagentity.TagTypeDbInstance,
|
||||
Name: me.Name,
|
||||
Children: authCertTags,
|
||||
}
|
||||
|
||||
@@ -13,8 +13,3 @@ type DbSql interface {
|
||||
type dbSqlAppImpl struct {
|
||||
base.AppImpl[*entity.DbSql, repository.DbSql]
|
||||
}
|
||||
|
||||
// 注入DbSqlRepo
|
||||
func (d *dbSqlAppImpl) InjectDbSqlRepo(repo repository.DbSql) {
|
||||
d.Repo = repo
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/application/dto"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/dbm/sqlparser"
|
||||
@@ -12,26 +13,21 @@ import (
|
||||
"mayfly-go/internal/db/imsg"
|
||||
flowapp "mayfly-go/internal/flow/application"
|
||||
flowentity "mayfly-go/internal/flow/domain/entity"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/i18n"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"os"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DbSqlExecReq struct {
|
||||
DbId uint64
|
||||
Db string
|
||||
Sql string // 需要执行的sql,支持多条
|
||||
SqlFile *os.File // sql文件
|
||||
Remark string // 执行备注
|
||||
DbConn *dbi.DbConn
|
||||
CheckFlow bool // 是否检查存储审批流程
|
||||
}
|
||||
|
||||
type sqlExecParam struct {
|
||||
DbConn *dbi.DbConn
|
||||
Sql string // 执行的sql
|
||||
@@ -41,18 +37,25 @@ type sqlExecParam struct {
|
||||
SqlExecRecord *entity.DbSqlExec // sql执行记录
|
||||
}
|
||||
|
||||
type DbSqlExecRes struct {
|
||||
Sql string `json:"sql"` // 执行的sql
|
||||
ErrorMsg string `json:"errorMsg"` // 若执行失败,则将失败内容记录到该字段
|
||||
Columns []*dbi.QueryColumn `json:"columns"` // 响应的列信息
|
||||
Res []map[string]any `json:"res"` // 响应结果
|
||||
// progressCategory sql文件执行进度消息类型
|
||||
const progressCategory = "execSqlFileProgress"
|
||||
|
||||
// progressMsg sql文件执行进度消息
|
||||
type progressMsg struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ExecutedStatements int `json:"executedStatements"`
|
||||
Terminated bool `json:"terminated"`
|
||||
}
|
||||
|
||||
type DbSqlExec interface {
|
||||
flowapp.FlowBizHandler
|
||||
|
||||
// 执行sql
|
||||
Exec(ctx context.Context, execSqlReq *DbSqlExecReq) ([]*DbSqlExecRes, error)
|
||||
Exec(ctx context.Context, execSqlReq *dto.DbSqlExecReq) ([]*dto.DbSqlExecRes, error)
|
||||
|
||||
// ExecReader 从reader中读取sql并执行
|
||||
ExecReader(ctx context.Context, execReader *dto.SqlReaderExec) error
|
||||
|
||||
// 根据条件删除sql执行记录
|
||||
DeleteBy(ctx context.Context, condition *entity.DbSqlExec) error
|
||||
@@ -62,13 +65,14 @@ type DbSqlExec interface {
|
||||
}
|
||||
|
||||
type dbSqlExecAppImpl struct {
|
||||
dbApp Db `inject:"DbApp"`
|
||||
dbSqlExecRepo repository.DbSqlExec `inject:"DbSqlExecRepo"`
|
||||
dbApp Db `inject:"T"`
|
||||
dbSqlExecRepo repository.DbSqlExec `inject:"T"`
|
||||
|
||||
flowProcdefApp flowapp.Procdef `inject:"ProcdefApp"`
|
||||
flowProcdefApp flowapp.Procdef `inject:"T"`
|
||||
msgApp msgapp.Msg `inject:"T"`
|
||||
}
|
||||
|
||||
func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq, sql string) *entity.DbSqlExec {
|
||||
func createSqlExecRecord(ctx context.Context, execSqlReq *dto.DbSqlExecReq, sql string) *entity.DbSqlExec {
|
||||
dbSqlExecRecord := new(entity.DbSqlExec)
|
||||
dbSqlExecRecord.DbId = execSqlReq.DbId
|
||||
dbSqlExecRecord.Db = execSqlReq.Db
|
||||
@@ -79,7 +83,7 @@ func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq, sql stri
|
||||
return dbSqlExecRecord
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) ([]*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *dto.DbSqlExecReq) ([]*dto.DbSqlExecRes, error) {
|
||||
dbConn := execSqlReq.DbConn
|
||||
execSql := execSqlReq.Sql
|
||||
sp := dbConn.GetDialect().GetSQLParser()
|
||||
@@ -89,13 +93,13 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
flowProcdef = d.flowProcdefApp.GetProcdefByCodePath(ctx, dbConn.Info.CodePath...)
|
||||
}
|
||||
|
||||
allExecRes := make([]*DbSqlExecRes, 0)
|
||||
allExecRes := make([]*dto.DbSqlExecRes, 0)
|
||||
|
||||
stmts, err := sp.Parse(execSql)
|
||||
// sql解析失败,则使用默认方式切割
|
||||
if err != nil {
|
||||
sqlparser.SQLSplit(strings.NewReader(execSql), func(oneSql string) error {
|
||||
var execRes *DbSqlExecRes
|
||||
var execRes *dto.DbSqlExecRes
|
||||
var err error
|
||||
|
||||
dbSqlExecRecord := createSqlExecRecord(ctx, execSqlReq, oneSql)
|
||||
@@ -120,7 +124,7 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
// 执行错误
|
||||
if err != nil {
|
||||
if execRes == nil {
|
||||
execRes = &DbSqlExecRes{Sql: oneSql}
|
||||
execRes = &dto.DbSqlExecRes{Sql: oneSql}
|
||||
}
|
||||
execRes.ErrorMsg = err.Error()
|
||||
} else {
|
||||
@@ -133,7 +137,7 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
}
|
||||
|
||||
for _, stmt := range stmts {
|
||||
var execRes *DbSqlExecRes
|
||||
var execRes *dto.DbSqlExecRes
|
||||
var err error
|
||||
|
||||
sql := stmt.GetText()
|
||||
@@ -172,7 +176,7 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
|
||||
if err != nil {
|
||||
if execRes == nil {
|
||||
execRes = &DbSqlExecRes{Sql: sql}
|
||||
execRes = &dto.DbSqlExecRes{Sql: sql}
|
||||
}
|
||||
execRes.ErrorMsg = err.Error()
|
||||
} else {
|
||||
@@ -184,6 +188,64 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
return allExecRes, nil
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) ExecReader(ctx context.Context, execReader *dto.SqlReaderExec) error {
|
||||
dbConn := execReader.DbConn
|
||||
|
||||
clientId := execReader.ClientId
|
||||
filename := stringx.Truncate(execReader.Filename, 20, 10, "...")
|
||||
la := contextx.GetLoginAccount(ctx)
|
||||
needSendMsg := la != nil && clientId != ""
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errInfo := anyx.ToString(err)
|
||||
logx.Errorf("exec sql reader error: %s", errInfo)
|
||||
if needSendMsg {
|
||||
errInfo = stringx.Truncate(errInfo, 300, 10, "...")
|
||||
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] execution failure: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)).WithClientId(clientId))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
if needSendMsg {
|
||||
defer ws.SendJsonMsg(ws.UserId(la.Id), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: true,
|
||||
}).WithCategory(progressCategory))
|
||||
}
|
||||
|
||||
err := sqlparser.SQLSplit(execReader.Reader, func(sql string) error {
|
||||
if executedStatements%50 == 0 {
|
||||
if needSendMsg {
|
||||
ws.SendJsonMsg(ws.UserId(la.Id), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
|
||||
Id: progressId,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: false,
|
||||
}).WithCategory(progressCategory))
|
||||
}
|
||||
}
|
||||
|
||||
executedStatements++
|
||||
if _, err := dbConn.Exec(sql); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needSendMsg {
|
||||
d.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), "execution success").WithClientId(clientId))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FlowDbExecSqlBizForm struct {
|
||||
DbId uint64 `json:"dbId"` // 库id
|
||||
DbName string `json:"dbName"` // 库名
|
||||
@@ -211,7 +273,7 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *fl
|
||||
return nil, err
|
||||
}
|
||||
|
||||
execRes, err := d.Exec(contextx.NewLoginAccount(&model.LoginAccount{Id: procinst.CreatorId, Username: procinst.Creator}), &DbSqlExecReq{
|
||||
execRes, err := d.Exec(contextx.NewLoginAccount(&model.LoginAccount{Id: procinst.CreatorId, Username: procinst.Creator}), &dto.DbSqlExecReq{
|
||||
DbId: execSqlBizForm.DbId,
|
||||
Db: execSqlBizForm.DbName,
|
||||
Sql: execSqlBizForm.Sql,
|
||||
@@ -257,7 +319,7 @@ func (d *dbSqlExecAppImpl) saveSqlExecLog(dbSqlExecRecord *entity.DbSqlExec, res
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, sqlExecParam *sqlExecParam) (*dto.DbSqlExecRes, error) {
|
||||
maxCount := config.GetDbms().MaxResultSet
|
||||
selectStmt := sqlExecParam.Stmt
|
||||
selectSql := sqlExecParam.Sql
|
||||
@@ -314,7 +376,7 @@ func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
return d.doQuery(ctx, sqlExecParam.DbConn, selectSql)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doOtherRead(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doOtherRead(ctx context.Context, sqlExecParam *sqlExecParam) (*dto.DbSqlExecRes, error) {
|
||||
selectSql := sqlExecParam.Sql
|
||||
sqlExecParam.SqlExecRecord.Type = entity.DbSqlExecTypeQuery
|
||||
|
||||
@@ -327,7 +389,7 @@ func (d *dbSqlExecAppImpl) doOtherRead(ctx context.Context, sqlExecParam *sqlExe
|
||||
return d.doQuery(ctx, sqlExecParam.DbConn, selectSql)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doExecDDL(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doExecDDL(ctx context.Context, sqlExecParam *sqlExecParam) (*dto.DbSqlExecRes, error) {
|
||||
selectSql := sqlExecParam.Sql
|
||||
sqlExecParam.SqlExecRecord.Type = entity.DbSqlExecTypeDDL
|
||||
|
||||
@@ -340,7 +402,7 @@ func (d *dbSqlExecAppImpl) doExecDDL(ctx context.Context, sqlExecParam *sqlExecP
|
||||
return d.doExec(ctx, sqlExecParam.DbConn, selectSql)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecParam) (*dto.DbSqlExecRes, error) {
|
||||
dbConn := sqlExecParam.DbConn
|
||||
|
||||
if procdef := sqlExecParam.Procdef; procdef != nil {
|
||||
@@ -365,7 +427,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
tableSources := updatestmt.TableSources.TableSources
|
||||
// 不支持多表更新记录旧值
|
||||
if len(tableSources) != 1 {
|
||||
logx.ErrorContext(ctx, "Update SQL - logging old values only supports single-table updates")
|
||||
logx.ErrorContext(ctx, "update SQL - logging old values only supports single-table updates")
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
@@ -379,21 +441,21 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
}
|
||||
|
||||
if tableName == "" {
|
||||
logx.ErrorContext(ctx, "Update SQL - failed to get table name")
|
||||
logx.ErrorContext(ctx, "update SQL - failed to get table name")
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
execRecord.Table = tableName
|
||||
|
||||
whereStr := updatestmt.Where.GetText()
|
||||
if whereStr == "" {
|
||||
logx.ErrorContext(ctx, "Update SQL - there is no where condition")
|
||||
if updatestmt.Where == nil {
|
||||
logx.ErrorContext(ctx, "update SQL - there is no where condition")
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
whereStr := updatestmt.Where.GetText()
|
||||
|
||||
// 获取表主键列名,排除使用别名
|
||||
primaryKey, err := dbConn.GetMetadata().GetPrimaryKey(tableName)
|
||||
if err != nil {
|
||||
logx.ErrorfContext(ctx, "Update SQL - failed to get primary key column: %s", err.Error())
|
||||
logx.ErrorfContext(ctx, "update SQL - failed to get primary key column: %s", err.Error())
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
@@ -417,12 +479,12 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
nowRec++
|
||||
res = append(res, row)
|
||||
if nowRec == maxRec {
|
||||
return errorx.NewBiz(fmt.Sprintf("Update SQL - the maximum number of updated queries is exceeded: %d", maxRec))
|
||||
return errorx.NewBiz(fmt.Sprintf("update SQL - the maximum number of updated queries is exceeded: %d", maxRec))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logx.ErrorfContext(ctx, "Update SQL - failed to get the updated old value: %s", err.Error())
|
||||
logx.ErrorfContext(ctx, "update SQL - failed to get the updated old value: %s", err.Error())
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
execRecord.OldValue = jsonx.ToStr(res)
|
||||
@@ -430,7 +492,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecParam) (*dto.DbSqlExecRes, error) {
|
||||
if procdef := sqlExecParam.Procdef; procdef != nil {
|
||||
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "delete")); needStartProc {
|
||||
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
|
||||
@@ -454,7 +516,7 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
tableSources := deletestmt.TableSources.TableSources
|
||||
// 不支持多表删除记录旧值
|
||||
if len(tableSources) != 1 {
|
||||
logx.ErrorContext(ctx, "Delete SQL - logging old values only supports single-table deletion")
|
||||
logx.ErrorContext(ctx, "delete SQL - logging old values only supports single-table deletion")
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
@@ -468,14 +530,14 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
}
|
||||
|
||||
if tableName == "" {
|
||||
logx.ErrorContext(ctx, "Delete SQL - failed to get table name")
|
||||
logx.ErrorContext(ctx, "delete SQL - failed to get table name")
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
execRecord.Table = tableName
|
||||
|
||||
whereStr := deletestmt.Where.GetText()
|
||||
if whereStr == "" {
|
||||
logx.ErrorContext(ctx, "Delete SQL - there is no where condition")
|
||||
logx.ErrorContext(ctx, "delete SQL - there is no where condition")
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
@@ -487,7 +549,7 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, sqlExecParam *sqlExecParam) (*dto.DbSqlExecRes, error) {
|
||||
if procdef := sqlExecParam.Procdef; procdef != nil {
|
||||
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "insert")); needStartProc {
|
||||
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
|
||||
@@ -513,19 +575,19 @@ func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, sqlExecParam *sqlExecPa
|
||||
return d.doExec(ctx, sqlExecParam.DbConn, sqlExecParam.Sql)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doQuery(ctx context.Context, dbConn *dbi.DbConn, sql string) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doQuery(ctx context.Context, dbConn *dbi.DbConn, sql string) (*dto.DbSqlExecRes, error) {
|
||||
cols, res, err := dbConn.QueryContext(ctx, sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DbSqlExecRes{
|
||||
return &dto.DbSqlExecRes{
|
||||
Sql: sql,
|
||||
Columns: cols,
|
||||
Res: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) doExec(ctx context.Context, dbConn *dbi.DbConn, sql string) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doExec(ctx context.Context, dbConn *dbi.DbConn, sql string) (*dto.DbSqlExecRes, error) {
|
||||
rowsAffected, err := dbConn.ExecContext(ctx, sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -534,7 +596,7 @@ func (d *dbSqlExecAppImpl) doExec(ctx context.Context, dbConn *dbi.DbConn, sql s
|
||||
res := make([]map[string]any, 0)
|
||||
res = append(res, collx.Kvs("rowsAffected", rowsAffected))
|
||||
|
||||
return &DbSqlExecRes{
|
||||
return &dto.DbSqlExecRes{
|
||||
Columns: []*dbi.QueryColumn{
|
||||
{Name: "rowsAffected", Type: "number"},
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user