feat: 数据库迁移至文件支持文件保存天数等

This commit is contained in:
meilin.huang
2024-10-22 20:39:44 +08:00
parent ea3c70a8a8
commit 44a1bd626e
19 changed files with 1043 additions and 1041 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);

View File

@@ -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: {

View File

@@ -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)}`
);

View File

@@ -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" />

View File

@@ -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"` // 选中需要迁移的表

View File

@@ -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"` // 创建表前是否删除表

View File

@@ -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")
}

View File

@@ -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)

View File

@@ -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)
}
// 删除数据

View File

@@ -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)

View File

@@ -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"` // 选中需要迁移的表

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.8.9"
Version = "v1.9.0"
)
func GetAppInfo() string {

View File

@@ -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 '删除时间',

View File

@@ -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;