mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-12-25 17:16:33 +08:00
feat: 数据库迁移至文件支持文件保存天数等
This commit is contained in:
@@ -15,7 +15,7 @@ const config = {
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.8.9',
|
||||
version: 'v1.9.0',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,42 +1,26 @@
|
||||
<template>
|
||||
<div class="db-transfer-edit">
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
:before-close="cancel"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
width="850px"
|
||||
>
|
||||
<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>
|
||||
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
|
||||
<el-tabs v-model="tabActiveName">
|
||||
<el-tab-pane label="基本信息" :name="basicTab">
|
||||
<el-form-item>
|
||||
<el-row class="w100" style="padding-bottom: 20px">
|
||||
<el-col :span="18">
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
|
||||
<el-form-item prop="taskName" label="任务名" required>
|
||||
<el-input v-model.trim="form.taskName" placeholder="请输入任务名" auto-complete="off" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item prop="status" label="启用状态">
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
inline-prompt
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
:active-value="1"
|
||||
:inactive-value="-1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-row class="w100" style="padding-bottom: 20px">
|
||||
<el-col :span="8">
|
||||
<el-row class="w100">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="status" label="启用状态">
|
||||
<el-switch v-model="form.status" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="-1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="cronAble" label="定时迁移" required>
|
||||
<el-radio-group v-model="form.cronAble">
|
||||
<el-radio label="是" :value="1" />
|
||||
@@ -44,15 +28,14 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="cron" label="cron" :required="form.cronAble == 1">
|
||||
<CrontabInput v-model="form.cron" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="srcDbId" label="源数据库" required>
|
||||
<el-form-item prop="cron" label="cron" :required="form.cronAble == 1">
|
||||
<CrontabInput v-model="form.cron" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="srcDbId" label="源数据库" class="w100" required>
|
||||
<db-select-tree
|
||||
placeholder="请选择源数据库"
|
||||
v-model:db-id="form.srcDbId"
|
||||
@@ -64,29 +47,18 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-row class="w100">
|
||||
<el-col :span="13">
|
||||
<el-form-item prop="mode" label="迁移方式" required>
|
||||
<el-radio-group v-model="form.mode">
|
||||
<el-radio label="迁移到数据库" :value="1" />
|
||||
<el-radio label="迁移到文件(自动命名)" :value="2" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="strategy" label="迁移策略" required>
|
||||
<el-radio-group v-model="form.strategy">
|
||||
<el-radio label="全量" :value="1" />
|
||||
<el-radio label="增量(暂不可用)" :value="2" disabled />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.mode === 2" prop="targetFileDbType" label="文件数据库类型" :required="form.mode === 2">
|
||||
<el-select style="width: 100%" v-model="form.targetFileDbType" placeholder="请选择数据库类型" clearable filterable>
|
||||
<el-form-item v-if="form.mode === 2">
|
||||
<el-row class="w100">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="targetFileDbType" label="文件数据库类型" :required="form.mode === 2">
|
||||
<el-select v-model="form.targetFileDbType" placeholder="数据库类型" clearable filterable>
|
||||
<el-option
|
||||
v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
|
||||
:key="key"
|
||||
@@ -101,8 +73,28 @@
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-form-item v-if="form.mode == 1" prop="targetDbId" label="目标数据库" :required="form.mode === 1">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文件保留天数">
|
||||
<el-input-number v-model="form.fileSaveDays" :min="-1" :max="1000">
|
||||
<template #suffix>
|
||||
<span>天</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="strategy" label="迁移策略" required>
|
||||
<el-radio-group v-model="form.strategy">
|
||||
<el-radio label="全量" :value="1" />
|
||||
<el-radio label="增量(暂不可用)" :value="2" disabled />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.mode == 1" prop="targetDbId" label="目标数据库" class="w100" :required="form.mode === 1">
|
||||
<db-select-tree
|
||||
placeholder="请选择目标数据库"
|
||||
v-model:db-id="form.targetDbId"
|
||||
@@ -121,21 +113,16 @@
|
||||
<el-radio label="小写" :value="3" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!--<el-form-item prop="deleteTable" label="创建前删除表" required>-->
|
||||
<!-- <el-radio-group v-model="form.deleteTable">-->
|
||||
<!-- <el-radio label="是" :value="1" />-->
|
||||
<!-- <el-radio label="否" :value="2" />-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!--</el-form-item>-->
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="数据库对象" :name="tableTab" :disabled="!baseFieldCompleted">
|
||||
|
||||
<el-divider content-position="left">数据库对象</el-divider>
|
||||
<el-form-item>
|
||||
<el-input v-model="state.filterSrcTableText" style="width: 240px" placeholder="过滤表" />
|
||||
<el-input v-model="state.filterSrcTableText" placeholder="过滤表" size="small" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item class="w100">
|
||||
<el-tree
|
||||
ref="srcTreeRef"
|
||||
style="width: 760px; max-height: 400px; overflow-y: auto"
|
||||
class="w100"
|
||||
style="max-height: 200px; overflow-y: auto"
|
||||
default-expand-all
|
||||
:expand-on-click-node="false"
|
||||
:data="state.srcTableTree"
|
||||
@@ -145,8 +132,6 @@
|
||||
:filter-node-method="filterSrcTableTreeNode"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -155,12 +140,12 @@
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { nextTick, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
|
||||
@@ -168,6 +153,7 @@ import CrontabInput from '@/components/crontab/CrontabInput.vue';
|
||||
import { getDbDialect, getDbDialectMap } from '@/views/ops/db/dialect';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import _ from 'lodash';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
@@ -223,9 +209,6 @@ const rules = {
|
||||
|
||||
const dbForm: any = ref(null);
|
||||
|
||||
const basicTab = 'basic';
|
||||
const tableTab = 'table';
|
||||
|
||||
type FormData = {
|
||||
id?: number;
|
||||
taskName: string;
|
||||
@@ -234,6 +217,7 @@ type FormData = {
|
||||
cron: string;
|
||||
mode: 1 | 2;
|
||||
targetFileDbType?: string;
|
||||
fileSaveDays?: number;
|
||||
dbType: 1 | 2;
|
||||
srcDbId?: number;
|
||||
srcDbName?: string;
|
||||
@@ -270,7 +254,6 @@ const srcTableListDisabled = ref(false);
|
||||
const defaultKeys = ['tab-check', 'all', 'table-list'];
|
||||
|
||||
const state = reactive({
|
||||
tabActiveName: 'basic',
|
||||
form: basicFormData,
|
||||
submitForm: {} as any,
|
||||
srcTableFields: [] as string[],
|
||||
@@ -293,20 +276,14 @@ const state = reactive({
|
||||
],
|
||||
});
|
||||
|
||||
const { tabActiveName, form, submitForm } = toRefs(state);
|
||||
const { form, submitForm } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveExec } = dbApi.saveDbTransferTask.useApi(submitForm);
|
||||
|
||||
// 基础字段信息是否填写完整
|
||||
const baseFieldCompleted = computed(() => {
|
||||
return state.form.srcDbId && (state.form.targetDbId || state.form.targetFileDbType);
|
||||
});
|
||||
|
||||
watch(dialogVisible, async (newValue: boolean) => {
|
||||
if (!newValue) {
|
||||
return;
|
||||
}
|
||||
state.tabActiveName = 'basic';
|
||||
const propsData = props.data as any;
|
||||
if (!propsData?.id) {
|
||||
let d = {} as FormData;
|
||||
@@ -447,10 +424,4 @@ const cancel = () => {
|
||||
emit('cancel');
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.db-transfer-edit {
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -47,12 +47,8 @@
|
||||
</template>
|
||||
|
||||
<template #suffix="{ data }">
|
||||
<span v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{
|
||||
` ${data.params.size}`
|
||||
}}</span>
|
||||
<span v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{
|
||||
` ${data.params.dbTableSize}`
|
||||
}}</span>
|
||||
<span v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
|
||||
<span v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{ ` ${data.params.dbTableSize}` }}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</Pane>
|
||||
@@ -80,8 +76,7 @@
|
||||
|
||||
<template v-if="!dbConfig.locationTreeNode">
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-button @click="locationNowTreeNode(null)" title="定位至左侧树的指定位置" icon="Location"
|
||||
link></el-button>
|
||||
<el-button @click="locationNowTreeNode(null)" title="定位至左侧树的指定位置" icon="Location" link></el-button>
|
||||
</template>
|
||||
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
@@ -160,8 +155,7 @@
|
||||
v-model="state.activeName"
|
||||
class="h100"
|
||||
>
|
||||
<el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key"
|
||||
:key="dt.key">
|
||||
<el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
|
||||
<template #label>
|
||||
<el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
|
||||
<template #reference>
|
||||
@@ -259,7 +253,7 @@ import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { format as sqlFormatter } from 'sql-formatter';
|
||||
import MonacoEditor from "@/components/monaco/MonacoEditor.vue";
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
|
||||
const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
|
||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||
@@ -391,10 +385,12 @@ const getNodeTypeTables = (params: any) => {
|
||||
let tableKey = `${params.id}.${params.db}.table-menu`;
|
||||
let sqlKey = getSqlMenuNodeKey(params.id, params.db);
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams({
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu)
|
||||
.withParams({
|
||||
...params,
|
||||
key: tableKey
|
||||
}).withIcon(TableIcon),
|
||||
key: tableKey,
|
||||
})
|
||||
.withIcon(TableIcon),
|
||||
new TagTreeNode(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({ ...params, key: sqlKey }).withIcon(SqlIcon),
|
||||
];
|
||||
};
|
||||
@@ -481,7 +477,7 @@ const NodeTypeTable = new NodeType(SqlExecNodeType.Table)
|
||||
new ContextmenuItem('renameTable', '重命名').withIcon('edit').withOnClick((data: any) => onRenameTable(data)),
|
||||
new ContextmenuItem('editTable', '编辑表').withIcon('edit').withOnClick((data: any) => onEditTable(data)),
|
||||
new ContextmenuItem('delTable', '删除表').withIcon('Delete').withOnClick((data: any) => onDeleteTable(data)),
|
||||
new ContextmenuItem('ddl', 'DDL').withIcon('Delete').withOnClick((data: any) => onGenDdl(data)),
|
||||
new ContextmenuItem('ddl', 'DDL').withIcon('Document').withOnClick((data: any) => onGenDdl(data)),
|
||||
])
|
||||
.withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||
const params = nodeData.params;
|
||||
@@ -563,11 +559,7 @@ const dbConfig = useStorage('dbConfig', DbThemeConfig);
|
||||
const serverInfoReqParam = ref({
|
||||
instanceId: 0,
|
||||
});
|
||||
const {
|
||||
execute: getDbServerInfo,
|
||||
isFetching: loadingServerInfo,
|
||||
data: dbServerInfo
|
||||
} = dbApi.getInstanceServerInfo.useApi<any>(serverInfoReqParam);
|
||||
const { execute: getDbServerInfo, isFetching: loadingServerInfo, data: dbServerInfo } = dbApi.getInstanceServerInfo.useApi<any>(serverInfoReqParam);
|
||||
|
||||
const autoOpenResourceStore = useAutoOpenResource();
|
||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
|
||||
|
||||
@@ -1,46 +1,33 @@
|
||||
<template>
|
||||
<div class="sync-task-edit">
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
:before-close="cancel"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
width="850px"
|
||||
>
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="45%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
|
||||
<el-tabs v-model="tabActiveName" style="height: 450px">
|
||||
<el-tabs v-model="tabActiveName">
|
||||
<el-tab-pane label="基本信息" :name="basicTab">
|
||||
<el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="11">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="taskName" label="任务名" required>
|
||||
<el-input v-model.trim="form.taskName" placeholder="请输入同步任务名" auto-complete="off" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="11">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="taskCron" label="cron" required>
|
||||
<CrontabInput v-model="form.taskCron" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2">
|
||||
<el-form-item prop="status" label="状态" label-width="60" required>
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
inline-prompt
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
:active-value="1"
|
||||
:inactive-value="-1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="status" label="状态" label-width="60" required>
|
||||
<el-switch v-model="form.status" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="-1" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="srcDbId" label="源数据库" required>
|
||||
<db-select-tree
|
||||
placeholder="请选择源数据库"
|
||||
@@ -220,7 +207,18 @@
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-drawer>
|
||||
|
||||
<!-- <el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
:before-close="cancel"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
width="850px"
|
||||
>
|
||||
</el-dialog> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -233,6 +231,7 @@ import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { DbInst, registerDbCompletionItemProvider } from '@/views/ops/db/db';
|
||||
import { compatibleDuplicateStrategy, DbType, DuplicateStrategy, getDbDialect } from '@/views/ops/db/dialect';
|
||||
import CrontabInput from '@/components/crontab/CrontabInput.vue';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
||||
@@ -82,7 +82,7 @@ const functions: EditorCompletionItem[] = [
|
||||
{
|
||||
label: 'sqlite_compileoption_used',
|
||||
insertText: 'sqlite_compileoption_used(X)',
|
||||
description: '检查SQLite编译时是否使用了指定的编译选项'
|
||||
description: '检查SQLite编译时是否使用了指定的编译选项',
|
||||
},
|
||||
{ label: 'sqlite_source_id', insertText: 'sqlite_source_id()', description: '获取sqlite源代码标识符' },
|
||||
{ label: 'sqlite_version', insertText: 'sqlite_version()', description: '获取sqlite版本' },
|
||||
@@ -105,13 +105,13 @@ const functions: EditorCompletionItem[] = [
|
||||
{
|
||||
label: 'time',
|
||||
insertText: 'time(time-value[, modifier, ...])',
|
||||
description: '将日期和时间字符串转换为特定的日期和时间格式'
|
||||
description: '将日期和时间字符串转换为特定的日期和时间格式',
|
||||
},
|
||||
{ label: 'datetime', insertText: 'datetime(time-value[, modifier, ...])', description: '计算日期和时间的儒略日数' },
|
||||
{
|
||||
label: 'julianday',
|
||||
insertText: 'julianday(time-value[, modifier, ...])',
|
||||
description: '将日期和时间格式化为指定的字符串'
|
||||
description: '将日期和时间格式化为指定的字符串',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -149,10 +149,7 @@ class SqliteDialect implements DbDialect {
|
||||
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `SELECT *
|
||||
FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||
pageNum,
|
||||
limit
|
||||
)};`;
|
||||
FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`;
|
||||
}
|
||||
|
||||
getPageSql(pageNum: number, limit: number) {
|
||||
@@ -170,7 +167,7 @@ class SqliteDialect implements DbDialect {
|
||||
notNull: true,
|
||||
pri: true,
|
||||
auto_increment: true,
|
||||
remark: '主键ID'
|
||||
remark: '主键ID',
|
||||
},
|
||||
{
|
||||
name: 'creator_id',
|
||||
@@ -181,7 +178,7 @@ class SqliteDialect implements DbDialect {
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '创建人id'
|
||||
remark: '创建人id',
|
||||
},
|
||||
{
|
||||
name: 'creator',
|
||||
@@ -214,7 +211,7 @@ class SqliteDialect implements DbDialect {
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '修改人id'
|
||||
remark: '修改人id',
|
||||
},
|
||||
{
|
||||
name: 'updator',
|
||||
@@ -225,7 +222,7 @@ class SqliteDialect implements DbDialect {
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '修改姓名'
|
||||
remark: '修改姓名',
|
||||
},
|
||||
{
|
||||
name: 'update_time',
|
||||
@@ -299,11 +296,15 @@ class SqliteDialect implements DbDialect {
|
||||
return sql.join(';');
|
||||
}
|
||||
|
||||
getModifyColumnSql(tableData: any, tableName: string, changeData: {
|
||||
getModifyColumnSql(
|
||||
tableData: any,
|
||||
tableName: string,
|
||||
changeData: {
|
||||
del: RowDefinition[];
|
||||
add: RowDefinition[];
|
||||
upd: RowDefinition[]
|
||||
}): string {
|
||||
upd: RowDefinition[];
|
||||
}
|
||||
): string {
|
||||
// sqlite修改表结构需要先删除再创建
|
||||
|
||||
// 1.删除旧表索引 DROP INDEX "main"."aa";
|
||||
@@ -341,9 +342,7 @@ class SqliteDialect implements DbDialect {
|
||||
// 生成sql
|
||||
sql.push(
|
||||
`INSERT INTO ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} (${insertFields.join(',')})
|
||||
SELECT ${queryFields.join(
|
||||
','
|
||||
)}
|
||||
SELECT ${queryFields.join(',')}
|
||||
FROM ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(oldTableName)}`
|
||||
);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
:before-close="cancelSaveTeam"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
size="40%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
|
||||
|
||||
@@ -8,6 +8,7 @@ type DbTransferTaskForm struct {
|
||||
Cron string `json:"cron"` // 定时任务cron表达式
|
||||
Mode int `binding:"required" json:"mode"` // 数据迁移方式,1、迁移到数据库 2、迁移到文件
|
||||
TargetFileDbType string `json:"targetFileDbType"` // 目标文件数据库类型
|
||||
FileSaveDays int `json:"fileSaveDays"` // 文件保存天数
|
||||
Status int `json:"status" form:"status"` // 启用状态 1启用 -1禁用
|
||||
|
||||
CheckedKeys string `binding:"required" json:"checkedKeys"` // 选中需要迁移的表
|
||||
|
||||
@@ -19,6 +19,7 @@ type DbTransferTaskListVO struct {
|
||||
Cron string `json:"cron"` // 定时任务cron表达式
|
||||
Mode int `json:"mode"` // 数据迁移方式,1、迁移到数据库 2、迁移到文件
|
||||
TargetFileDbType string `json:"targetFileDbType"` // 目标文件数据库类型
|
||||
FileSaveDays int `json:"fileSaveDays"` // 文件保存天数
|
||||
|
||||
CheckedKeys string `json:"checkedKeys"` // 选中需要迁移的表
|
||||
DeleteTable int `json:"deleteTable"` // 创建表前是否删除表
|
||||
|
||||
@@ -31,8 +31,10 @@ func Init() {
|
||||
//if err := GetDbBinlogApp().Init(); err != nil {
|
||||
// panic(fmt.Sprintf("初始化 DbBinlogApp 失败: %v", err))
|
||||
//}
|
||||
|
||||
GetDataSyncTaskApp().InitCronJob()
|
||||
GetDbTransferTaskApp().InitCronJob()
|
||||
GetDbTransferTaskApp().TimerDeleteTransferFile()
|
||||
InitDbFlowHandler()
|
||||
})()
|
||||
}
|
||||
@@ -56,6 +58,7 @@ func GetDbBinlogApp() *DbBinlogApp {
|
||||
func GetDataSyncTaskApp() DataSyncTask {
|
||||
return ioc.Get[DataSyncTask]("DbDataSyncTaskApp")
|
||||
}
|
||||
|
||||
func GetDbTransferTaskApp() DbTransferTask {
|
||||
return ioc.Get[DbTransferTask]("DbTransferTaskApp")
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ type DbTransferTask interface {
|
||||
IsRunning(taskId uint64) bool
|
||||
|
||||
Stop(ctx context.Context, taskId uint64) error
|
||||
|
||||
// TimerDeleteTransferFile 定时删除迁移文件
|
||||
TimerDeleteTransferFile()
|
||||
}
|
||||
|
||||
type dbTransferAppImpl struct {
|
||||
@@ -80,6 +83,9 @@ func (app *dbTransferAppImpl) Save(ctx context.Context, taskEntity *entity.DbTra
|
||||
} else {
|
||||
err = app.UpdateById(ctx, taskEntity)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 视情况添加或删除任务
|
||||
task, err := app.GetById(taskEntity.Id)
|
||||
if err != nil {
|
||||
@@ -549,6 +555,29 @@ func (app *dbTransferAppImpl) transferIndex(_ context.Context, tableInfo dbi.Tab
|
||||
return targetDialect.CreateIndex(tableInfo, indexs)
|
||||
}
|
||||
|
||||
func (d *dbTransferAppImpl) TimerDeleteTransferFile() {
|
||||
logx.Debug("开始定时删除迁移文件...")
|
||||
scheduler.AddFun("@every 100m", func() {
|
||||
dts, err := d.ListByCond(model.NewCond().Eq("mode", entity.DbTransferTaskModeFile).Ge("file_save_days", 1))
|
||||
if err != nil {
|
||||
logx.Errorf("定时获取数据库迁移至文件任务失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
for _, dt := range dts {
|
||||
needDelFiles, err := d.transferFileApp.ListByCond(model.NewCond().Eq("task_id", dt.Id).Le("create_time", time.Now().AddDate(0, 0, -dt.FileSaveDays)))
|
||||
if err != nil {
|
||||
logx.Errorf("定时获取迁移文件失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
for _, nf := range needDelFiles {
|
||||
if err := d.transferFileApp.Delete(context.Background(), nf.Id); err != nil {
|
||||
logx.Errorf("定时删除迁移文件失败: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MarkRunning 标记任务执行中
|
||||
func (app *dbTransferAppImpl) MarkRunning(taskId uint64) {
|
||||
cache.Set(fmt.Sprintf("mayfly:db:transfer:%d", taskId), 1, -1)
|
||||
|
||||
@@ -54,7 +54,7 @@ func (app *dbTransferFileAppImpl) Delete(ctx context.Context, id ...uint64) erro
|
||||
|
||||
// 删除对应的文件
|
||||
for _, file := range arr {
|
||||
_ = app.fileApp.Remove(ctx, file.FileKey)
|
||||
app.fileApp.Remove(ctx, file.FileKey)
|
||||
}
|
||||
|
||||
// 删除数据
|
||||
|
||||
@@ -89,7 +89,7 @@ func (sd *SqliteMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||
func (sd *SqliteMetaData) getDataTypes(dataType string) (string, string, string) {
|
||||
matches := dataTypeRegexp.FindStringSubmatch(dataType)
|
||||
if len(matches) == 0 {
|
||||
return "", "", ""
|
||||
return dataType, "", ""
|
||||
}
|
||||
return matches[1], matches[2], matches[3]
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (sd *SqliteMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
||||
} else {
|
||||
column.CharMaxLength = cast.ToInt(length)
|
||||
}
|
||||
column.DataType = dbi.ColumnDataType(dataType)
|
||||
column.DataType = dbi.ColumnDataType(strings.ToLower(dataType))
|
||||
columnHelper.FixColumn(&column)
|
||||
|
||||
columns = append(columns, column)
|
||||
|
||||
@@ -15,6 +15,7 @@ type DbTransferTask struct {
|
||||
Cron string `orm:"column(cron)" json:"cron"` // 定时任务cron表达式
|
||||
Mode int8 `orm:"column(mode)" json:"mode"` // 数据迁移方式,1、迁移到数据库 2、迁移到文件
|
||||
TargetFileDbType string `orm:"column(target_file_db_type)" json:"targetFileDbType"` // 目标文件数据库类型
|
||||
FileSaveDays int `json:"fileSaveDays"` // 文件保存天数
|
||||
TaskKey string `orm:"column(key)" json:"taskKey"` // 定时任务唯一uuid key
|
||||
|
||||
CheckedKeys string `orm:"column(checked_keys)" json:"checkedKeys"` // 选中需要迁移的表
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/pkg/eventbus"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
@@ -13,6 +19,26 @@ func InitIoc() {
|
||||
ioc.Register(new(machineCmdConfAppImpl), ioc.WithComponentName("MachineCmdConfApp"))
|
||||
}
|
||||
|
||||
func Init() {
|
||||
sync.OnceFunc(func() {
|
||||
GetMachineCronJobApp().InitCronJob()
|
||||
|
||||
GetMachineApp().TimerUpdateStats()
|
||||
|
||||
GetMachineTermOpApp().TimerDeleteTermOp()
|
||||
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineFile", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
return GetMachineFileApp().DeleteByCond(ctx, &entity.MachineFile{MachineId: me.Id})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineScript", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
return GetMachineScriptApp().DeleteByCond(ctx, &entity.MachineScript{MachineId: me.Id})
|
||||
})
|
||||
})()
|
||||
}
|
||||
|
||||
func GetMachineApp() Machine {
|
||||
return ioc.Get[Machine]("MachineApp")
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/infrastructure/persistence"
|
||||
"mayfly-go/internal/machine/router"
|
||||
"mayfly-go/pkg/eventbus"
|
||||
"mayfly-go/pkg/global"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -18,23 +13,5 @@ func init() {
|
||||
application.InitIoc()
|
||||
})
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
initialize.AddInitFunc(Init)
|
||||
}
|
||||
|
||||
func Init() {
|
||||
application.GetMachineCronJobApp().InitCronJob()
|
||||
|
||||
application.GetMachineApp().TimerUpdateStats()
|
||||
|
||||
application.GetMachineTermOpApp().TimerDeleteTermOp()
|
||||
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineFile", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
return application.GetMachineFileApp().DeleteByCond(ctx, &entity.MachineFile{MachineId: me.Id})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineScript", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
return application.GetMachineScriptApp().DeleteByCond(ctx, &entity.MachineScript{MachineId: me.Id})
|
||||
})
|
||||
initialize.AddInitFunc(application.Init)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
const (
|
||||
AppName = "mayfly-go"
|
||||
Version = "v1.8.9"
|
||||
Version = "v1.9.0"
|
||||
)
|
||||
|
||||
func GetAppInfo() string {
|
||||
|
||||
Binary file not shown.
@@ -69,6 +69,7 @@ CREATE TABLE `t_db_transfer_task` (
|
||||
`task_key` varchar(100) NULL comment '定时任务唯一uuid key',
|
||||
`mode` TINYINT(3) NOT NULL DEFAULT 1 comment '数据迁移方式,1、迁移到数据库 2、迁移到文件',
|
||||
`target_file_db_type` varchar(200) NULL comment '目标文件语言类型,类型枚举同target_db_type',
|
||||
`file_save_days` int NULL comment '文件保存天数',
|
||||
`status` tinyint(3) NOT NULL DEFAULT '1' comment '启用状态 1启用 -1禁用',
|
||||
`upd_field_src` varchar(100) DEFAULT NULL COMMENT '更新值来源字段,默认同更新字段,如果查询结果指定了字段别名且与原更新字段不一致,则取这个字段值为当前更新值',
|
||||
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
|
||||
|
||||
@@ -11,7 +11,7 @@ INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `we
|
||||
|
||||
-- 新增数据库迁移相关的系统配置
|
||||
DELETE FROM `t_sys_config` WHERE `key` = 'DbBackupRestore';
|
||||
UPDATE `t_sys_config` SET param = '[{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"}]' WHERE `key`='MachineConfig';
|
||||
UPDATE `t_sys_config` SET params = '[{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"}]' WHERE `key`='MachineConfig';
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('文件配置', 'FileConfig', '[{"model":"basePath","name":"基础路径","placeholder":"默认为可执行文件对应目录下./file"}]', '{"basePath":"./file"}', '系统文件相关配置', 'admin,', '2024-10-20 22:30:01', 1, 'admin', '2024-10-21 13:51:17', 1, 'admin', 0, NULL);
|
||||
|
||||
-- 数据库迁移到文件
|
||||
@@ -22,6 +22,7 @@ ALTER TABLE `t_db_transfer_task`
|
||||
ADD COLUMN `task_key` varchar(100) NULL comment '定时任务唯一uuid key',
|
||||
ADD COLUMN `mode` TINYINT(3) NOT NULL DEFAULT 1 comment '数据迁移方式,1、迁移到数据库 2、迁移到文件',
|
||||
ADD COLUMN `target_file_db_type` varchar(200) NULL comment '目标文件语言类型,类型枚举同target_db_type',
|
||||
ADD COLUMN `file_save_days` int NULL comment '文件保存天数',
|
||||
ADD COLUMN `status` tinyint(3) NOT NULL DEFAULT '1' comment '启用状态 1启用 -1禁用';
|
||||
|
||||
UPDATE `t_db_transfer_task` SET mode = 1 WHERE 1=1;
|
||||
Reference in New Issue
Block a user