mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	refactor: code review、数据库备份恢复支持ssh隧道操作
This commit is contained in:
		@@ -50,7 +50,7 @@ export const usePageTable = (
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let res = await api.request(sp);
 | 
			
		||||
            dataCallBack && (res = dataCallBack(res));
 | 
			
		||||
            dataCallBack && (res = await dataCallBack(res));
 | 
			
		||||
 | 
			
		||||
            if (pageable) {
 | 
			
		||||
                state.tableData = res.list;
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ const columns = [
 | 
			
		||||
    TableColumn.new('enabled', '是否启用'),
 | 
			
		||||
    TableColumn.new('lastResult', '执行结果'),
 | 
			
		||||
    TableColumn.new('lastTime', '执行时间').isTime(),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(160).fixedRight(),
 | 
			
		||||
    TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight(),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const emptyQuery = {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
            width="700px"
 | 
			
		||||
        >
 | 
			
		||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                <el-tabs v-model="tabActiveName" style="height: 450px">
 | 
			
		||||
                    <el-tab-pane label="基本信息" name="basic">
 | 
			
		||||
                        <el-form-item prop="taskName" label="任务名" required>
 | 
			
		||||
                            <el-input v-model.trim="form.taskName" placeholder="请输入数据库别名" auto-complete="off" />
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
                            />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="dataSql" label="数据sql" required>
 | 
			
		||||
                            <monaco-editor height="300px" class="task-sql" language="sql" v-model="form.dataSql" />
 | 
			
		||||
                            <monaco-editor height="200px" class="task-sql" language="sql" v-model="form.dataSql" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
@@ -116,9 +116,6 @@ import { DbInst, registerDbCompletionItemProvider } from '@/views/ops/db/db';
 | 
			
		||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    data: {
 | 
			
		||||
        type: [Boolean, Object],
 | 
			
		||||
    },
 | 
			
		||||
@@ -130,6 +127,8 @@ const props = defineProps({
 | 
			
		||||
//定义事件
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
 | 
			
		||||
 | 
			
		||||
const dialogVisible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
    taskName: [
 | 
			
		||||
        {
 | 
			
		||||
@@ -180,7 +179,6 @@ const basicFormData = {
 | 
			
		||||
} as FormData;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    tabActiveName: 'basic',
 | 
			
		||||
    form: basicFormData,
 | 
			
		||||
    submitForm: {} as any,
 | 
			
		||||
@@ -218,18 +216,18 @@ const loadDbTables = async (dbId: number, db: string) => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const { dialogVisible, tabActiveName, form, submitForm } = toRefs(state);
 | 
			
		||||
const { tabActiveName, form, submitForm } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const { isFetching: saveBtnLoading, execute: saveExec } = dbApi.saveDatasyncTask.useApi(submitForm);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    if (!state.dialogVisible) {
 | 
			
		||||
watch(dialogVisible, async (newValue: boolean) => {
 | 
			
		||||
    if (!newValue) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.tabActiveName = 'basic';
 | 
			
		||||
    if (newValue.data?.id) {
 | 
			
		||||
        let data = await dbApi.getDatasyncTask.request({ taskId: newValue.data?.id });
 | 
			
		||||
    const propsData = props.data as any;
 | 
			
		||||
    if (propsData?.id) {
 | 
			
		||||
        let data = await dbApi.getDatasyncTask.request({ taskId: propsData?.id });
 | 
			
		||||
        state.form = data;
 | 
			
		||||
        try {
 | 
			
		||||
            state.form.fieldMap = JSON.parse(data.fieldMap);
 | 
			
		||||
@@ -316,6 +314,7 @@ watch(tabActiveName, async (newValue: string) => {
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleGetSrcFields = async () => {
 | 
			
		||||
    // 执行sql,获取字段信息
 | 
			
		||||
    if (!state.form.dataSql || !state.form.dataSql.trim()) {
 | 
			
		||||
@@ -362,6 +361,7 @@ const handleGetSrcFields = async () => {
 | 
			
		||||
 | 
			
		||||
    state.previewRes = res;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleGetTargetFields = async () => {
 | 
			
		||||
    // 查询目标表下的字段信息
 | 
			
		||||
    if (state.form.targetDbName && state.form.targetTableName) {
 | 
			
		||||
@@ -412,7 +412,7 @@ const btnOk = async () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    dialogVisible.value = false;
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -150,7 +150,7 @@ const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema).withNodeCl
 | 
			
		||||
        overflow-x: hidden;
 | 
			
		||||
        width: 560px;
 | 
			
		||||
        .el-tree {
 | 
			
		||||
            height: 200px;
 | 
			
		||||
            height: 150px;
 | 
			
		||||
            overflow-y: auto;
 | 
			
		||||
            overflow-x: hidden;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -114,7 +114,8 @@ const search = async () => {
 | 
			
		||||
    pageTableRef.value.search();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const parseData = async (dataList: any) => {
 | 
			
		||||
const parseData = async (res: any) => {
 | 
			
		||||
    const dataList = res.list;
 | 
			
		||||
    // 填充机器信息
 | 
			
		||||
    for (let x of dataList) {
 | 
			
		||||
        const machineId = x.machineId;
 | 
			
		||||
@@ -137,7 +138,7 @@ const parseData = async (dataList: any) => {
 | 
			
		||||
        x.machineIp = machine?.ip;
 | 
			
		||||
        x.machineName = machine?.name;
 | 
			
		||||
    }
 | 
			
		||||
    return dataList;
 | 
			
		||||
    return res;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/captcha"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/cryptox"
 | 
			
		||||
@@ -87,7 +88,7 @@ func (a *LdapLogin) getUser(userName string, cols ...string) (*sysentity.Account
 | 
			
		||||
 | 
			
		||||
func (a *LdapLogin) createUser(userName, displayName string) {
 | 
			
		||||
	account := &sysentity.Account{Username: userName}
 | 
			
		||||
	account.SetBaseInfo(nil)
 | 
			
		||||
	account.SetBaseInfo(model.IdGenTypeNone, nil)
 | 
			
		||||
	account.Name = displayName
 | 
			
		||||
	biz.ErrIsNil(a.AccountApp.Create(context.TODO(), account))
 | 
			
		||||
	// 将 LADP 用户本地密码设置为空,不允许本地登录
 | 
			
		||||
 
 | 
			
		||||
@@ -169,10 +169,7 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 进行登录
 | 
			
		||||
	account := &sysentity.Account{
 | 
			
		||||
		Model: model.Model{DeletedModel: model.DeletedModel{Id: accountId}},
 | 
			
		||||
	}
 | 
			
		||||
	err = a.AccountApp.GetBy(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
 | 
			
		||||
	account, err := a.AccountApp.GetById(new(sysentity.Account), accountId, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
 | 
			
		||||
 | 
			
		||||
	clientIp := getIpAndRegion(rc)
 | 
			
		||||
 
 | 
			
		||||
@@ -137,3 +137,19 @@ func (d *DbBackup) GetDbNamesWithoutBackup(rc *req.Ctx) {
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取未配置定时备份的数据库名称失败: %v")
 | 
			
		||||
	rc.ResData = dbNamesWithoutBackup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPageList 获取数据库备份历史
 | 
			
		||||
// @router /api/dbs/:dbId/backups/:backupId/histories [GET]
 | 
			
		||||
func (d *DbBackup) GetHistoryPageList(rc *req.Ctx) {
 | 
			
		||||
	dbId := uint64(ginx.PathParamInt(rc.GinCtx, "dbId"))
 | 
			
		||||
	biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
 | 
			
		||||
	db, err := d.DbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
 | 
			
		||||
 | 
			
		||||
	queryCond, page := ginx.BindQueryAndPage[*entity.DbBackupHistoryQuery](rc.GinCtx, new(entity.DbBackupHistoryQuery))
 | 
			
		||||
	queryCond.DbInstanceId = db.InstanceId
 | 
			
		||||
	queryCond.InDbNames = strings.Fields(db.Database)
 | 
			
		||||
	res, err := d.DbBackupApp.GetHistoryPageList(queryCond, page, new([]vo.DbBackupHistory))
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/internal/db/api/vo"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/internal/db/domain/entity"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DbBackupHistory struct {
 | 
			
		||||
	DbBackupHistoryApp *application.DbBackupHistoryApp
 | 
			
		||||
	DbApp              application.Db
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPageList 获取数据库备份历史
 | 
			
		||||
// @router /api/dbs/:dbId/backups/:backupId/histories [GET]
 | 
			
		||||
func (d *DbBackupHistory) GetPageList(rc *req.Ctx) {
 | 
			
		||||
	dbId := uint64(ginx.PathParamInt(rc.GinCtx, "dbId"))
 | 
			
		||||
	biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
 | 
			
		||||
	db, err := d.DbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
 | 
			
		||||
 | 
			
		||||
	queryCond, page := ginx.BindQueryAndPage[*entity.DbBackupHistoryQuery](rc.GinCtx, new(entity.DbBackupHistoryQuery))
 | 
			
		||||
	queryCond.DbInstanceId = db.InstanceId
 | 
			
		||||
	queryCond.InDbNames = strings.Fields(db.Database)
 | 
			
		||||
	res, err := d.DbBackupHistoryApp.GetPageList(queryCond, page, new([]vo.DbBackupHistory))
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete 删除数据库备份历史
 | 
			
		||||
// @router /api/dbs/:dbId/backups/:backupId/histories/:historyId [DELETE]
 | 
			
		||||
func (d *DbBackupHistory) Delete(rc *req.Ctx) {
 | 
			
		||||
	// todo delete backup histories
 | 
			
		||||
	panic("implement me")
 | 
			
		||||
}
 | 
			
		||||
@@ -3,8 +3,6 @@ package api
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"mayfly-go/internal/db/api/form"
 | 
			
		||||
	"mayfly-go/internal/db/api/vo"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
@@ -15,11 +13,13 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DataSyncTask struct {
 | 
			
		||||
	DataSyncTaskApp application.DataSyncTask
 | 
			
		||||
	DataSyncLogApp  application.DataSyncLog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DataSyncTask) Tasks(rc *req.Ctx) {
 | 
			
		||||
@@ -31,7 +31,7 @@ func (d *DataSyncTask) Tasks(rc *req.Ctx) {
 | 
			
		||||
 | 
			
		||||
func (d *DataSyncTask) Logs(rc *req.Ctx) {
 | 
			
		||||
	queryCond, page := ginx.BindQueryAndPage[*entity.DataSyncLogQuery](rc.GinCtx, new(entity.DataSyncLogQuery))
 | 
			
		||||
	res, err := d.DataSyncLogApp.GetTaskLogList(queryCond, page, new([]vo.DataSyncLogListVO))
 | 
			
		||||
	res, err := d.DataSyncTaskApp.GetTaskLogList(queryCond, page, new([]vo.DataSyncLogListVO))
 | 
			
		||||
	biz.ErrIsNil(err)
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -92,21 +92,18 @@ func (d *DbRestore) walk(rc *req.Ctx, fn func(ctx context.Context, taskId uint64
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete 删除数据库恢复任务
 | 
			
		||||
// @router /api/dbs/:dbId/restores/:taskId [DELETE]
 | 
			
		||||
func (d *DbRestore) Delete(rc *req.Ctx) {
 | 
			
		||||
	err := d.walk(rc, d.DbRestoreApp.Delete)
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "删除数据库恢复任务失败: %v")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Enable 删除数据库恢复任务
 | 
			
		||||
// @router /api/dbs/:dbId/restores/:taskId/enable [PUT]
 | 
			
		||||
func (d *DbRestore) Enable(rc *req.Ctx) {
 | 
			
		||||
	err := d.walk(rc, d.DbRestoreApp.Enable)
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "启用数据库恢复任务失败: %v")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Disable 删除数据库恢复任务
 | 
			
		||||
// @router /api/dbs/:dbId/restores/:taskId/disable [PUT]
 | 
			
		||||
func (d *DbRestore) Disable(rc *req.Ctx) {
 | 
			
		||||
	err := d.walk(rc, d.DbRestoreApp.Disable)
 | 
			
		||||
@@ -124,3 +121,14 @@ func (d *DbRestore) GetDbNamesWithoutRestore(rc *req.Ctx) {
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取未配置定时备份的数据库名称失败: %v")
 | 
			
		||||
	rc.ResData = dbNamesWithoutRestore
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取数据库备份历史
 | 
			
		||||
// @router /api/dbs/:dbId/restores/:restoreId/histories [GET]
 | 
			
		||||
func (d *DbRestore) GetHistoryPageList(rc *req.Ctx) {
 | 
			
		||||
	queryCond := &entity.DbRestoreHistoryQuery{
 | 
			
		||||
		DbRestoreId: uint64(ginx.PathParamInt(rc.GinCtx, "restoreId")),
 | 
			
		||||
	}
 | 
			
		||||
	res, err := d.DbRestoreApp.GetHistoryPageList(queryCond, ginx.GetPageParam(rc.GinCtx), new([]vo.DbRestoreHistory))
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/internal/db/api/vo"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/internal/db/domain/entity"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DbRestoreHistory struct {
 | 
			
		||||
	InstanceApp         application.Instance
 | 
			
		||||
	DbRestoreHistoryApp *application.DbRestoreHistoryApp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPageList 获取数据库备份历史
 | 
			
		||||
// @router /api/dbs/:dbId/restores/:restoreId/histories [GET]
 | 
			
		||||
func (d *DbRestoreHistory) GetPageList(rc *req.Ctx) {
 | 
			
		||||
	queryCond := &entity.DbRestoreHistoryQuery{
 | 
			
		||||
		DbRestoreId: uint64(ginx.PathParamInt(rc.GinCtx, "restoreId")),
 | 
			
		||||
	}
 | 
			
		||||
	res, err := d.DbRestoreHistoryApp.GetPageList(queryCond, ginx.GetPageParam(rc.GinCtx), new([]vo.DbRestoreHistory))
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete 删除数据库备份历史
 | 
			
		||||
// @router /api/dbs/:dbId/restores/:restoreId/histories/:historyId [DELETE]
 | 
			
		||||
func (d *DbRestoreHistory) Delete(rc *req.Ctx) {
 | 
			
		||||
	// todo delete restore histories
 | 
			
		||||
	panic("implement me")
 | 
			
		||||
}
 | 
			
		||||
@@ -27,3 +27,12 @@ func (backup *DbBackup) MarshalJSON() ([]byte, error) {
 | 
			
		||||
	backup.IntervalDay = uint64(backup.Interval / time.Hour / 24)
 | 
			
		||||
	return json.Marshal((*dbBackup)(backup))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DbBackupHistory 数据库备份历史
 | 
			
		||||
type DbBackupHistory struct {
 | 
			
		||||
	Id         uint64    `json:"id"`
 | 
			
		||||
	DbBackupId uint64    `json:"dbBackupId"`
 | 
			
		||||
	CreateTime time.Time `json:"createTime"`
 | 
			
		||||
	DbName     string    `json:"dbName"` // 数据库名称
 | 
			
		||||
	Name       string    `json:"name"`   // 备份历史名称
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
// DbBackupHistory 数据库备份历史
 | 
			
		||||
type DbBackupHistory struct {
 | 
			
		||||
	Id         uint64    `json:"id"`
 | 
			
		||||
	DbBackupId uint64    `json:"dbBackupId"`
 | 
			
		||||
	CreateTime time.Time `json:"createTime"`
 | 
			
		||||
	DbName     string    `json:"dbName"` // 数据库名称
 | 
			
		||||
	Name       string    `json:"name"`   // 备份历史名称
 | 
			
		||||
}
 | 
			
		||||
@@ -29,3 +29,9 @@ func (restore *DbRestore) MarshalJSON() ([]byte, error) {
 | 
			
		||||
	restore.IntervalDay = uint64(restore.Interval / time.Hour / 24)
 | 
			
		||||
	return json.Marshal((*dbBackup)(restore))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DbRestoreHistory 数据库备份历史
 | 
			
		||||
type DbRestoreHistory struct {
 | 
			
		||||
	Id          uint64 `json:"id"`
 | 
			
		||||
	DbRestoreId uint64 `json:"dbRestoreId"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
// DbRestoreHistory 数据库备份历史
 | 
			
		||||
type DbRestoreHistory struct {
 | 
			
		||||
	Id          uint64 `json:"id"`
 | 
			
		||||
	DbRestoreId uint64 `json:"dbRestoreId"`
 | 
			
		||||
}
 | 
			
		||||
@@ -14,12 +14,9 @@ var (
 | 
			
		||||
	dbSqlExecApp DbSqlExec
 | 
			
		||||
	dbSqlApp     DbSql
 | 
			
		||||
	dbBackupApp  *DbBackupApp
 | 
			
		||||
	dbBackupHistoryApp  *DbBackupHistoryApp
 | 
			
		||||
	dbRestoreApp *DbRestoreApp
 | 
			
		||||
	dbRestoreHistoryApp *DbRestoreHistoryApp
 | 
			
		||||
	dbBinlogApp  *DbBinlogApp
 | 
			
		||||
	dataSyncApp  DataSyncTask
 | 
			
		||||
	dataSyncLogApp      DataSyncLog
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var repositories *repository.Repositories
 | 
			
		||||
@@ -41,8 +38,7 @@ func Init() {
 | 
			
		||||
		dbApp = newDbApp(persistence.GetDbRepo(), persistence.GetDbSqlRepo(), instanceApp, tagapp.GetTagTreeApp())
 | 
			
		||||
		dbSqlExecApp = newDbSqlExecApp(persistence.GetDbSqlExecRepo())
 | 
			
		||||
		dbSqlApp = newDbSqlApp(persistence.GetDbSqlRepo())
 | 
			
		||||
		dataSyncApp = newDataSyncApp(persistence.GetDataSyncTaskRepo())
 | 
			
		||||
		dataSyncLogApp = newDataSyncLogApp(persistence.GetDataSyncLogRepo())
 | 
			
		||||
		dataSyncApp = newDataSyncApp(persistence.GetDataSyncTaskRepo(), persistence.GetDataSyncLogRepo())
 | 
			
		||||
 | 
			
		||||
		dbBackupApp, err = newDbBackupApp(repositories, dbApp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -52,14 +48,7 @@ func Init() {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("初始化 dbRestoreApp 失败: %v", err))
 | 
			
		||||
		}
 | 
			
		||||
		dbBackupHistoryApp, err = newDbBackupHistoryApp(repositories)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("初始化 dbBackupHistoryApp 失败: %v", err))
 | 
			
		||||
		}
 | 
			
		||||
		dbRestoreHistoryApp, err = newDbRestoreHistoryApp(repositories)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("初始化 dbRestoreHistoryApp 失败: %v", err))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dbBinlogApp, err = newDbBinlogApp(repositories, dbApp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("初始化 dbBinlogApp 失败: %v", err))
 | 
			
		||||
@@ -89,18 +78,10 @@ func GetDbBackupApp() *DbBackupApp {
 | 
			
		||||
	return dbBackupApp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDbBackupHistoryApp() *DbBackupHistoryApp {
 | 
			
		||||
	return dbBackupHistoryApp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDbRestoreApp() *DbRestoreApp {
 | 
			
		||||
	return dbRestoreApp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDbRestoreHistoryApp() *DbRestoreHistoryApp {
 | 
			
		||||
	return dbRestoreHistoryApp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDbBinlogApp() *DbBinlogApp {
 | 
			
		||||
	return dbBinlogApp
 | 
			
		||||
}
 | 
			
		||||
@@ -108,7 +89,3 @@ func GetDbBinlogApp() *DbBinlogApp {
 | 
			
		||||
func GetDataSyncTaskApp() DataSyncTask {
 | 
			
		||||
	return dataSyncApp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDataSyncLogApp() DataSyncLog {
 | 
			
		||||
	return dataSyncLogApp
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,7 @@ func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbm.DbConn, error
 | 
			
		||||
 | 
			
		||||
	var dbs []*entity.Db
 | 
			
		||||
	if err := d.ListByCond(&entity.Db{InstanceId: instanceId}, &dbs, "id", "database"); err != nil {
 | 
			
		||||
		return nil, errorx.NewBiz("获取数据库列表失败: ", err)
 | 
			
		||||
		return nil, errorx.NewBiz("获取数据库列表失败")
 | 
			
		||||
	}
 | 
			
		||||
	if len(dbs) == 0 {
 | 
			
		||||
		return nil, errorx.NewBiz("该实例未配置数据库, 请先进行配置")
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,12 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"mayfly-go/internal/db/domain/entity"
 | 
			
		||||
	"mayfly-go/internal/db/domain/repository"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newDbBackupApp(repositories *repository.Repositories, dbApp Db) (*DbBackupApp, error) {
 | 
			
		||||
@@ -75,6 +76,11 @@ func (app *DbBackupApp) GetDbNamesWithoutBackup(instanceId uint64, dbNames []str
 | 
			
		||||
	return app.backupRepo.GetDbNamesWithoutBackup(instanceId, dbNames)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPageList 分页获取数据库备份历史
 | 
			
		||||
func (app *DbBackupApp) GetHistoryPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	return app.backupHistoryRepo.GetHistories(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func withRunBackupTask(app *DbBackupApp) dbSchedulerOption[*entity.DbBackup] {
 | 
			
		||||
	return func(scheduler *dbScheduler[*entity.DbBackup]) {
 | 
			
		||||
		scheduler.RunTask = app.runTask
 | 
			
		||||
@@ -139,18 +145,18 @@ func NewIncUUID() (uuid.UUID, error) {
 | 
			
		||||
	return uid, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDbBackupHistoryApp(repositories *repository.Repositories) (*DbBackupHistoryApp, error) {
 | 
			
		||||
	app := &DbBackupHistoryApp{
 | 
			
		||||
		repo: repositories.BackupHistory,
 | 
			
		||||
	}
 | 
			
		||||
	return app, nil
 | 
			
		||||
}
 | 
			
		||||
// func newDbBackupHistoryApp(repositories *repository.Repositories) (*DbBackupHistoryApp, error) {
 | 
			
		||||
// 	app := &DbBackupHistoryApp{
 | 
			
		||||
// 		repo: repositories.BackupHistory,
 | 
			
		||||
// 	}
 | 
			
		||||
// 	return app, nil
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
type DbBackupHistoryApp struct {
 | 
			
		||||
	repo repository.DbBackupHistory
 | 
			
		||||
}
 | 
			
		||||
// type DbBackupHistoryApp struct {
 | 
			
		||||
// 	repo repository.DbBackupHistory
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// GetPageList 分页获取数据库备份历史
 | 
			
		||||
func (app *DbBackupHistoryApp) GetPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	return app.repo.GetHistories(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
}
 | 
			
		||||
// // GetPageList 分页获取数据库备份历史
 | 
			
		||||
// func (app *DbBackupHistoryApp) GetPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
// 	return app.repo.GetHistories(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
// }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,27 +24,36 @@ type DataSyncTask interface {
 | 
			
		||||
 | 
			
		||||
	Save(ctx context.Context, instanceEntity *entity.DataSyncTask) error
 | 
			
		||||
 | 
			
		||||
	// Delete 删除数据库信息
 | 
			
		||||
	Delete(ctx context.Context, id uint64) error
 | 
			
		||||
 | 
			
		||||
	InitCronJob()
 | 
			
		||||
 | 
			
		||||
	AddCronJob(taskEntity *entity.DataSyncTask)
 | 
			
		||||
 | 
			
		||||
	AddCronJobById(id uint64) error
 | 
			
		||||
 | 
			
		||||
	RemoveCronJob(taskEntity *entity.DataSyncTask)
 | 
			
		||||
 | 
			
		||||
	RemoveCronJobById(id uint64) error
 | 
			
		||||
 | 
			
		||||
	RemoveCronJobByKey(taskKey string)
 | 
			
		||||
 | 
			
		||||
	RunCronJob(id uint64)
 | 
			
		||||
 | 
			
		||||
	GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDataSyncApp(dataSyncRepo repository.DataSyncTask) DataSyncTask {
 | 
			
		||||
func newDataSyncApp(dataSyncRepo repository.DataSyncTask, dataSyncLogRepo repository.DataSyncLog) DataSyncTask {
 | 
			
		||||
	app := new(dataSyncAppImpl)
 | 
			
		||||
	app.Repo = dataSyncRepo
 | 
			
		||||
	app.dataSyncLogRepo = dataSyncLogRepo
 | 
			
		||||
	return app
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataSyncAppImpl struct {
 | 
			
		||||
	base.AppImpl[*entity.DataSyncTask, repository.DataSyncTask]
 | 
			
		||||
 | 
			
		||||
	dataSyncLogRepo repository.DataSyncLog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *dataSyncAppImpl) GetPageList(condition *entity.DataSyncTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
@@ -114,6 +123,10 @@ func (app *dataSyncAppImpl) changeRunningState(id uint64, state int8) {
 | 
			
		||||
func (app *dataSyncAppImpl) RunCronJob(id uint64) {
 | 
			
		||||
	// 查询最新的任务信息
 | 
			
		||||
	task, err := app.GetById(new(entity.DataSyncTask), id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logx.Warnf("[%d]任务不存在", id)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if task.RunningState == entity.DataSyncTaskRunStateRunning {
 | 
			
		||||
		logx.Warnf("数据同步任务正在执行中:%s => %s", task.TaskName, task.TaskKey)
 | 
			
		||||
		return
 | 
			
		||||
@@ -184,7 +197,7 @@ func (app *dataSyncAppImpl) RunCronJob(id uint64) {
 | 
			
		||||
		var fieldMap []map[string]string
 | 
			
		||||
		err = json.Unmarshal([]byte(task.FieldMap), &fieldMap)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			app.endRunning(task, entity.DataSyncTaskStateFail, fmt.Sprintf("解析字段映射json出错"), sql, resSize)
 | 
			
		||||
			app.endRunning(task, entity.DataSyncTaskStateFail, "解析字段映射json出错", sql, resSize)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -304,9 +317,10 @@ func (app *dataSyncAppImpl) endRunning(taskEntity *entity.DataSyncTask, state in
 | 
			
		||||
	app.saveLog(taskEntity.Id, state, msg, sql, resNum)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *dataSyncAppImpl) saveLog(taskId uint64, state int8, msg string, sql string, resNum int) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	_ = GetDataSyncLogApp().Insert(context.Background(), &entity.DataSyncLog{
 | 
			
		||||
	_ = app.dataSyncLogRepo.Insert(context.Background(), &entity.DataSyncLog{
 | 
			
		||||
		TaskId:      taskId,
 | 
			
		||||
		CreateTime:  &now,
 | 
			
		||||
		DataSqlFull: sql,
 | 
			
		||||
@@ -357,3 +371,7 @@ func (app *dataSyncAppImpl) InitCronJob() {
 | 
			
		||||
		_, _ = app.GetPageList(cond, pageParam, jobs)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *dataSyncAppImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	return app.dataSyncLogRepo.GetTaskLogList(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
package application
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/internal/db/domain/entity"
 | 
			
		||||
	"mayfly-go/internal/db/domain/repository"
 | 
			
		||||
	"mayfly-go/pkg/base"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DataSyncLog interface {
 | 
			
		||||
	base.App[*entity.DataSyncLog]
 | 
			
		||||
 | 
			
		||||
	// GetTaskLogList 分页获取数据库实例
 | 
			
		||||
	GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDataSyncLogApp(dataSyncRepo repository.DataSyncLog) DataSyncLog {
 | 
			
		||||
	app := new(dataSyncLogAppImpl)
 | 
			
		||||
	app.Repo = dataSyncRepo
 | 
			
		||||
	return app
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataSyncLogAppImpl struct {
 | 
			
		||||
	base.AppImpl[*entity.DataSyncLog, repository.DataSyncLog]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *dataSyncLogAppImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	return app.GetRepo().GetTaskLogList(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
}
 | 
			
		||||
@@ -73,6 +73,11 @@ func (app *DbRestoreApp) GetDbNamesWithoutRestore(instanceId uint64, dbNames []s
 | 
			
		||||
	return app.restoreRepo.GetDbNamesWithoutRestore(instanceId, dbNames)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 分页获取数据库备份历史
 | 
			
		||||
func (app *DbRestoreApp) GetHistoryPageList(condition *entity.DbRestoreHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	return app.restoreHistoryRepo.GetDbRestoreHistories(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *DbRestoreApp) runTask(ctx context.Context, task *entity.DbRestore) error {
 | 
			
		||||
	conn, err := app.dbApp.GetDbConnByInstanceId(task.DbInstanceId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -173,19 +178,3 @@ func withRunRestoreTask(app *DbRestoreApp) dbSchedulerOption[*entity.DbRestore]
 | 
			
		||||
		scheduler.RunTask = app.runTask
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDbRestoreHistoryApp(repositories *repository.Repositories) (*DbRestoreHistoryApp, error) {
 | 
			
		||||
	app := &DbRestoreHistoryApp{
 | 
			
		||||
		repo: repositories.RestoreHistory,
 | 
			
		||||
	}
 | 
			
		||||
	return app, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DbRestoreHistoryApp struct {
 | 
			
		||||
	repo repository.DbRestoreHistory
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPageList 分页获取数据库备份历史
 | 
			
		||||
func (app *DbRestoreHistoryApp) GetPageList(condition *entity.DbRestoreHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	return app.repo.GetDbRestoreHistories(condition, pageParam, toEntity, orderBy...)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@ func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq) *entity.
 | 
			
		||||
	dbSqlExecRecord.Db = execSqlReq.Db
 | 
			
		||||
	dbSqlExecRecord.Sql = execSqlReq.Sql
 | 
			
		||||
	dbSqlExecRecord.Remark = execSqlReq.Remark
 | 
			
		||||
	dbSqlExecRecord.SetBaseInfo(contextx.GetLoginAccount(ctx))
 | 
			
		||||
	dbSqlExecRecord.SetBaseInfo(model.IdGenTypeNone, contextx.GetLoginAccount(ctx))
 | 
			
		||||
	return dbSqlExecRecord
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,13 @@ import (
 | 
			
		||||
 | 
			
		||||
type DbProgram interface {
 | 
			
		||||
	Backup(ctx context.Context, backupHistory *entity.DbBackupHistory) (*entity.BinlogInfo, error)
 | 
			
		||||
 | 
			
		||||
	FetchBinlogs(ctx context.Context, downloadLatestBinlogFile bool, earliestBackupSequence, latestBinlogSequence int64) ([]*entity.BinlogFile, error)
 | 
			
		||||
 | 
			
		||||
	ReplayBinlog(ctx context.Context, originalDatabase, targetDatabase string, restoreInfo *RestoreInfo) error
 | 
			
		||||
 | 
			
		||||
	RestoreBackupHistory(ctx context.Context, dbName string, dbBackupId uint64, dbBackupHistoryUuid string) error
 | 
			
		||||
 | 
			
		||||
	GetBinlogEventPositionAtOrAfterTime(ctx context.Context, binlogName string, targetTime time.Time) (position int64, parseErr error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,21 +40,27 @@ func NewDbProgramMysql(dbConn *DbConn) *DbProgramMysql {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svc *DbProgramMysql) dbInfo() *DbInfo {
 | 
			
		||||
	return svc.dbConn.Info
 | 
			
		||||
	dbInfo := svc.dbConn.Info
 | 
			
		||||
	err := dbInfo.IfUseSshTunnelChangeIpPort()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logx.Errorf("通过ssh隧道连接db失败: %s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return dbInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svc *DbProgramMysql) getMysqlBin() *config.MysqlBin {
 | 
			
		||||
	if svc.mysqlBin != nil {
 | 
			
		||||
		return svc.mysqlBin
 | 
			
		||||
	}
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	var mysqlBin *config.MysqlBin
 | 
			
		||||
	switch svc.dbInfo().Type {
 | 
			
		||||
	switch dbInfo.Type {
 | 
			
		||||
	case DbTypeMariadb:
 | 
			
		||||
		mysqlBin = config.GetMysqlBin(config.ConfigKeyDbMariadbBin)
 | 
			
		||||
	case DbTypeMysql:
 | 
			
		||||
		mysqlBin = config.GetMysqlBin(config.ConfigKeyDbMysqlBin)
 | 
			
		||||
	default:
 | 
			
		||||
		panic(fmt.Sprintf("不兼容 MySQL 的数据库类型: %v", svc.dbInfo().Type))
 | 
			
		||||
		panic(fmt.Sprintf("不兼容 MySQL 的数据库类型: %v", dbInfo.Type))
 | 
			
		||||
	}
 | 
			
		||||
	svc.mysqlBin = mysqlBin
 | 
			
		||||
	return svc.mysqlBin
 | 
			
		||||
@@ -81,11 +87,12 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
 | 
			
		||||
		_ = os.Remove(tmpFile)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	args := []string{
 | 
			
		||||
		"--host", svc.dbInfo().Host,
 | 
			
		||||
		"--port", strconv.Itoa(svc.dbInfo().Port),
 | 
			
		||||
		"--user", svc.dbInfo().Username,
 | 
			
		||||
		"--password=" + svc.dbInfo().Password,
 | 
			
		||||
		"--host", dbInfo.Host,
 | 
			
		||||
		"--port", strconv.Itoa(dbInfo.Port),
 | 
			
		||||
		"--user", dbInfo.Username,
 | 
			
		||||
		"--password=" + dbInfo.Password,
 | 
			
		||||
		"--add-drop-database",
 | 
			
		||||
		"--result-file", tmpFile,
 | 
			
		||||
		"--single-transaction",
 | 
			
		||||
@@ -123,12 +130,13 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svc *DbProgramMysql) RestoreBackupHistory(ctx context.Context, dbName string, dbBackupId uint64, dbBackupHistoryUuid string) error {
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	args := []string{
 | 
			
		||||
		"--host", svc.dbInfo().Host,
 | 
			
		||||
		"--port", strconv.Itoa(svc.dbInfo().Port),
 | 
			
		||||
		"--host", dbInfo.Host,
 | 
			
		||||
		"--port", strconv.Itoa(dbInfo.Port),
 | 
			
		||||
		"--database", dbName,
 | 
			
		||||
		"--user", svc.dbInfo().Username,
 | 
			
		||||
		"--password=" + svc.dbInfo().Password,
 | 
			
		||||
		"--user", dbInfo.Username,
 | 
			
		||||
		"--password=" + dbInfo.Password,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fileName := filepath.Join(svc.getDbBackupDir(svc.dbInfo().InstanceId, dbBackupId),
 | 
			
		||||
@@ -157,7 +165,8 @@ func (svc *DbProgramMysql) downloadBinlogFilesOnServer(ctx context.Context, binl
 | 
			
		||||
		logx.Debug("No binlog file found on server to download")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.MkdirAll(svc.getBinlogDir(svc.dbInfo().InstanceId), os.ModePerm); err != nil {
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	if err := os.MkdirAll(svc.getBinlogDir(dbInfo.InstanceId), os.ModePerm); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "创建 binlog 目录失败: %q", svc.getBinlogDir(svc.dbInfo().InstanceId))
 | 
			
		||||
	}
 | 
			
		||||
	latestBinlogFileOnServer := binlogFilesOnServerSorted[len(binlogFilesOnServerSorted)-1]
 | 
			
		||||
@@ -166,7 +175,7 @@ func (svc *DbProgramMysql) downloadBinlogFilesOnServer(ctx context.Context, binl
 | 
			
		||||
		if isLatest && !downloadLatestBinlogFile {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		binlogFilePath := filepath.Join(svc.getBinlogDir(svc.dbInfo().InstanceId), fileOnServer.Name)
 | 
			
		||||
		binlogFilePath := filepath.Join(svc.getBinlogDir(dbInfo.InstanceId), fileOnServer.Name)
 | 
			
		||||
		logx.Debug("Downloading binlog file from MySQL server.", logx.String("path", binlogFilePath), logx.Bool("isLatest", isLatest))
 | 
			
		||||
		if err := svc.downloadBinlogFile(ctx, fileOnServer, isLatest); err != nil {
 | 
			
		||||
			logx.Error("下载 binlog 文件失败", logx.String("path", binlogFilePath), logx.String("error", err.Error()))
 | 
			
		||||
@@ -287,15 +296,16 @@ func (svc *DbProgramMysql) fetchBinlogs(ctx context.Context, downloadLatestBinlo
 | 
			
		||||
// It may keep growing as there are ongoing writes to the database. So we just need to check that
 | 
			
		||||
// the file size is larger or equal to the binlog file size we queried from the MySQL server earlier.
 | 
			
		||||
func (svc *DbProgramMysql) downloadBinlogFile(ctx context.Context, binlogFileToDownload *entity.BinlogFile, isLast bool) error {
 | 
			
		||||
	tempBinlogPrefix := filepath.Join(svc.getBinlogDir(svc.dbInfo().InstanceId), "tmp-")
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	tempBinlogPrefix := filepath.Join(svc.getBinlogDir(dbInfo.InstanceId), "tmp-")
 | 
			
		||||
	args := []string{
 | 
			
		||||
		binlogFileToDownload.Name,
 | 
			
		||||
		"--read-from-remote-server",
 | 
			
		||||
		// Verify checksum binlog events.
 | 
			
		||||
		"--verify-binlog-checksum",
 | 
			
		||||
		"--host", svc.dbInfo().Host,
 | 
			
		||||
		"--port", strconv.Itoa(svc.dbInfo().Port),
 | 
			
		||||
		"--user", svc.dbInfo().Username,
 | 
			
		||||
		"--host", dbInfo.Host,
 | 
			
		||||
		"--port", strconv.Itoa(dbInfo.Port),
 | 
			
		||||
		"--user", dbInfo.Username,
 | 
			
		||||
		"--raw",
 | 
			
		||||
		// With --raw this is a prefix for the file names.
 | 
			
		||||
		"--result-file", tempBinlogPrefix,
 | 
			
		||||
@@ -304,8 +314,8 @@ func (svc *DbProgramMysql) downloadBinlogFile(ctx context.Context, binlogFileToD
 | 
			
		||||
	cmd := exec.CommandContext(ctx, svc.getMysqlBin().MysqlbinlogPath, args...)
 | 
			
		||||
	// We cannot set password as a flag. Otherwise, there is warning message
 | 
			
		||||
	// "mysqlbinlog: [Warning] Using a password on the command line interface can be insecure."
 | 
			
		||||
	if svc.dbInfo().Password != "" {
 | 
			
		||||
		cmd.Env = append(cmd.Env, fmt.Sprintf("MYSQL_PWD=%s", svc.dbInfo().Password))
 | 
			
		||||
	if dbInfo.Password != "" {
 | 
			
		||||
		cmd.Env = append(cmd.Env, fmt.Sprintf("MYSQL_PWD=%s", dbInfo.Password))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logx.Debug("Downloading binlog files using mysqlbinlog:", cmd.String())
 | 
			
		||||
@@ -531,18 +541,19 @@ func (svc *DbProgramMysql) ReplayBinlog(ctx context.Context, originalDatabase, t
 | 
			
		||||
		"--stop-position", fmt.Sprintf("%d", restoreInfo.TargetPosition),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mysqlbinlogArgs = append(mysqlbinlogArgs, restoreInfo.GetBinlogPaths(svc.getBinlogDir(svc.dbInfo().InstanceId))...)
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	mysqlbinlogArgs = append(mysqlbinlogArgs, restoreInfo.GetBinlogPaths(svc.getBinlogDir(dbInfo.InstanceId))...)
 | 
			
		||||
 | 
			
		||||
	mysqlArgs := []string{
 | 
			
		||||
		"--host", svc.dbInfo().Host,
 | 
			
		||||
		"--port", strconv.Itoa(svc.dbInfo().Port),
 | 
			
		||||
		"--user", svc.dbInfo().Username,
 | 
			
		||||
		"--host", dbInfo.Host,
 | 
			
		||||
		"--port", strconv.Itoa(dbInfo.Port),
 | 
			
		||||
		"--user", dbInfo.Username,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if svc.dbInfo().Password != "" {
 | 
			
		||||
	if dbInfo.Password != "" {
 | 
			
		||||
		// The --password parameter of mysql/mysqlbinlog does not support the "--password PASSWORD" format (split by space).
 | 
			
		||||
		// If provided like that, the program will hang.
 | 
			
		||||
		mysqlArgs = append(mysqlArgs, fmt.Sprintf("--password=%s", svc.dbInfo().Password))
 | 
			
		||||
		mysqlArgs = append(mysqlArgs, fmt.Sprintf("--password=%s", dbInfo.Password))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mysqlbinlogCmd := exec.CommandContext(ctx, svc.getMysqlBin().MysqlbinlogPath, mysqlbinlogArgs...)
 | 
			
		||||
@@ -649,11 +660,12 @@ func runCmd(cmd *exec.Cmd) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svc *DbProgramMysql) execute(database string, sql string) error {
 | 
			
		||||
	dbInfo := svc.dbInfo()
 | 
			
		||||
	args := []string{
 | 
			
		||||
		"--host", svc.dbInfo().Host,
 | 
			
		||||
		"--port", strconv.Itoa(svc.dbInfo().Port),
 | 
			
		||||
		"--user", svc.dbInfo().Username,
 | 
			
		||||
		"--password=" + svc.dbInfo().Password,
 | 
			
		||||
		"--host", dbInfo.Host,
 | 
			
		||||
		"--port", strconv.Itoa(dbInfo.Port),
 | 
			
		||||
		"--user", dbInfo.Username,
 | 
			
		||||
		"--password=" + dbInfo.Password,
 | 
			
		||||
		"--execute", sql,
 | 
			
		||||
	}
 | 
			
		||||
	if len(database) > 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	machineapp "mayfly-go/internal/machine/application"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/logx"
 | 
			
		||||
	"mayfly-go/pkg/utils/anyx"
 | 
			
		||||
@@ -29,19 +28,10 @@ func getDmDB(d *DbInfo) (*sql.DB, error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 开启ssh隧道
 | 
			
		||||
	if d.SshTunnelMachineId > 0 {
 | 
			
		||||
		sshTunnelMachine, err := machineapp.GetMachineApp().GetSshTunnelMachine(d.SshTunnelMachineId)
 | 
			
		||||
	err := d.IfUseSshTunnelChangeIpPort()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
		exposedIp, exposedPort, err := sshTunnelMachine.OpenSshTunnel(fmt.Sprintf("db:%d", d.Id), d.Host, d.Port)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		d.Host = exposedIp
 | 
			
		||||
		d.Port = exposedPort
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dsn := fmt.Sprintf("dm://%s:%s@%s:%d/%s", d.Username, d.Password, d.Host, d.Port, dbParam)
 | 
			
		||||
	return sql.Open(driverName, dsn)
 | 
			
		||||
@@ -299,6 +289,7 @@ func (pd *DMDialect) WrapName(name string) string {
 | 
			
		||||
func (pd *DMDialect) PageSql(pageNum int, pageSize int) string {
 | 
			
		||||
	return fmt.Sprintf("LIMIT %d OFFSET %d", pageSize, (pageNum-1)*pageSize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *DMDialect) GetDataType(dbColumnType string) DataType {
 | 
			
		||||
	if regexp.MustCompile(`(?i)int|double|float|number|decimal|byte|bit`).MatchString(dbColumnType) {
 | 
			
		||||
		return DataTypeNumber
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,14 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/go-sql-driver/mysql"
 | 
			
		||||
	machineapp "mayfly-go/internal/machine/application"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/utils/anyx"
 | 
			
		||||
	"net"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-sql-driver/mysql"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getMysqlDB(d *DbInfo) (*sql.DB, error) {
 | 
			
		||||
@@ -212,6 +213,7 @@ func (pd *MysqlDialect) WrapName(name string) string {
 | 
			
		||||
func (pd *MysqlDialect) PageSql(pageNum int, pageSize int) string {
 | 
			
		||||
	return fmt.Sprintf("limit %d, %d", (pageNum-1)*pageSize, pageSize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *MysqlDialect) GetDataType(dbColumnType string) DataType {
 | 
			
		||||
	if regexp.MustCompile(`(?i)int|double|float|number|decimal|byte|bit`).MatchString(dbColumnType) {
 | 
			
		||||
		return DataTypeNumber
 | 
			
		||||
@@ -230,6 +232,7 @@ func (pd *MysqlDialect) GetDataType(dbColumnType string) DataType {
 | 
			
		||||
	}
 | 
			
		||||
	return DataTypeString
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *MysqlDialect) SaveBatch(conn *DbConn, tableName string, columns string, placeholder string, values [][]any) error {
 | 
			
		||||
	// 执行批量insert sql,mysql支持批量insert语法
 | 
			
		||||
	// insert into table_name (column1, column2, ...) values (value1, value2, ...), (value1, value2, ...), ...
 | 
			
		||||
 
 | 
			
		||||
@@ -291,6 +291,7 @@ func (pd *PgsqlDialect) WrapName(name string) string {
 | 
			
		||||
func (pd *PgsqlDialect) PageSql(pageNum int, pageSize int) string {
 | 
			
		||||
	return fmt.Sprintf("LIMIT %d OFFSET %d", pageSize, (pageNum-1)*pageSize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *PgsqlDialect) GetDataType(dbColumnType string) DataType {
 | 
			
		||||
	if regexp.MustCompile(`(?i)int|double|float|number|decimal|byte|bit`).MatchString(dbColumnType) {
 | 
			
		||||
		return DataTypeNumber
 | 
			
		||||
@@ -309,6 +310,7 @@ func (pd *PgsqlDialect) GetDataType(dbColumnType string) DataType {
 | 
			
		||||
	}
 | 
			
		||||
	return DataTypeString
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *PgsqlDialect) SaveBatch(conn *DbConn, tableName string, columns string, placeholder string, values [][]any) error {
 | 
			
		||||
	// 执行批量insert sql,跟mysql一样  pg或高斯支持批量insert语法
 | 
			
		||||
	// insert into table_name (column1, column2, ...) values (value1, value2, ...), (value1, value2, ...), ...
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package dbm
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	machineapp "mayfly-go/internal/machine/application"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/logx"
 | 
			
		||||
)
 | 
			
		||||
@@ -72,6 +73,24 @@ func (dbInfo *DbInfo) Conn() (*DbConn, error) {
 | 
			
		||||
	return dbc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 如果使用了ssh隧道,将其host port改变其本地映射host port
 | 
			
		||||
func (di *DbInfo) IfUseSshTunnelChangeIpPort() error {
 | 
			
		||||
	// 开启ssh隧道
 | 
			
		||||
	if di.SshTunnelMachineId > 0 {
 | 
			
		||||
		sshTunnelMachine, err := machineapp.GetMachineApp().GetSshTunnelMachine(di.SshTunnelMachineId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		exposedIp, exposedPort, err := sshTunnelMachine.OpenSshTunnel(fmt.Sprintf("db:%d", di.Id), di.Host, di.Port)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		di.Host = exposedIp
 | 
			
		||||
		di.Port = exposedPort
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取连接id
 | 
			
		||||
func GetDbConnId(dbId uint64, db string) string {
 | 
			
		||||
	if dbId == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ func (d *DataSyncTask) TableName() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataSyncLog struct {
 | 
			
		||||
	Id          uint64     `json:"id"`                           // 自增主键
 | 
			
		||||
	model.IdModel
 | 
			
		||||
	TaskId      uint64     `orm:"column(task_id)" json:"taskId"` // 任务表id
 | 
			
		||||
	CreateTime  *time.Time `orm:"column(create_time)" json:"createTime"`
 | 
			
		||||
	DataSqlFull string     `orm:"column(data_sql_full)" json:"dataSqlFull"` // 执行的完整sql
 | 
			
		||||
@@ -47,10 +47,6 @@ type DataSyncLog struct {
 | 
			
		||||
	Status      int8       `orm:"column(status)" json:"status"`             // 状态:1.成功  -1.失败
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DataSyncLog) SetBaseInfo(account *model.LoginAccount) {
 | 
			
		||||
	//TODO implement me
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DataSyncLog) TableName() string {
 | 
			
		||||
	return "t_db_data_sync_log"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ type DbBackupHistory interface {
 | 
			
		||||
 | 
			
		||||
	// GetDbBackupHistories 分页获取数据备份历史
 | 
			
		||||
	GetHistories(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
 | 
			
		||||
 | 
			
		||||
	GetLatestHistory(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error)
 | 
			
		||||
 | 
			
		||||
	GetEarliestHistory(instanceId uint64) (*entity.DbBackupHistory, error)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,14 @@ import (
 | 
			
		||||
 | 
			
		||||
type DbBinlogHistory interface {
 | 
			
		||||
	base.Repo[*entity.DbBinlogHistory]
 | 
			
		||||
 | 
			
		||||
	GetHistories(instanceId uint64, start, target *entity.BinlogInfo) ([]*entity.DbBinlogHistory, error)
 | 
			
		||||
 | 
			
		||||
	GetHistoryByTime(instanceId uint64, targetTime time.Time) (*entity.DbBinlogHistory, error)
 | 
			
		||||
 | 
			
		||||
	GetLatestHistory(instanceId uint64) (*entity.DbBinlogHistory, bool, error)
 | 
			
		||||
 | 
			
		||||
	InsertWithBinlogFiles(ctx context.Context, instanceId uint64, binlogFiles []*entity.BinlogFile) error
 | 
			
		||||
 | 
			
		||||
	Upsert(ctx context.Context, history *entity.DbBinlogHistory) error
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
package router
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"mayfly-go/internal/db/api"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitDbBackupRouter(router *gin.RouterGroup) {
 | 
			
		||||
@@ -32,6 +33,9 @@ func InitDbBackupRouter(router *gin.RouterGroup) {
 | 
			
		||||
		req.NewDelete(":dbId/backups/:backupId", d.Delete),
 | 
			
		||||
		// 获取未配置定时备份的数据库名称
 | 
			
		||||
		req.NewGet(":dbId/db-names-without-backup", d.GetDbNamesWithoutBackup),
 | 
			
		||||
 | 
			
		||||
		// 获取数据库备份历史
 | 
			
		||||
		req.NewGet(":dbId/backup-histories/", d.GetHistoryPageList),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.BatchSetGroup(dbs, reqs)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
package router
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"mayfly-go/internal/db/api"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitDbBackupHistoryRouter(router *gin.RouterGroup) {
 | 
			
		||||
	dbs := router.Group("/dbs")
 | 
			
		||||
 | 
			
		||||
	d := &api.DbBackupHistory{
 | 
			
		||||
		DbBackupHistoryApp: application.GetDbBackupHistoryApp(),
 | 
			
		||||
		DbApp:              application.GetDbApp(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqs := []*req.Conf{
 | 
			
		||||
		// 获取数据库备份历史
 | 
			
		||||
		req.NewGet(":dbId/backup-histories/", d.GetPageList),
 | 
			
		||||
		// 删除数据库备份历史
 | 
			
		||||
		req.NewDelete(":dbId/backups/:backupId/histories/:historyId", d.Delete),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.BatchSetGroup(dbs, reqs)
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,6 @@ func InitDbDataSyncRouter(router *gin.RouterGroup) {
 | 
			
		||||
 | 
			
		||||
	d := &api.DataSyncTask{
 | 
			
		||||
		DataSyncTaskApp: application.GetDataSyncTaskApp(),
 | 
			
		||||
		DataSyncLogApp:  application.GetDataSyncLogApp(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqs := [...]*req.Conf{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
package router
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"mayfly-go/internal/db/api"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitDbRestoreRouter(router *gin.RouterGroup) {
 | 
			
		||||
@@ -30,6 +31,9 @@ func InitDbRestoreRouter(router *gin.RouterGroup) {
 | 
			
		||||
		req.NewDelete(":dbId/restores/:restoreId", d.Delete),
 | 
			
		||||
		// 获取未配置定时恢复的数据库名称
 | 
			
		||||
		req.NewGet(":dbId/db-names-without-restore", d.GetDbNamesWithoutRestore),
 | 
			
		||||
 | 
			
		||||
		// 获取数据库备份历史
 | 
			
		||||
		req.NewGet(":dbId/restores/:restoreId/histories", d.GetHistoryPageList),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.BatchSetGroup(dbs, reqs)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
package router
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"mayfly-go/internal/db/api"
 | 
			
		||||
	"mayfly-go/internal/db/application"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitDbRestoreHistoryRouter(router *gin.RouterGroup) {
 | 
			
		||||
	dbs := router.Group("/dbs")
 | 
			
		||||
 | 
			
		||||
	d := &api.DbRestoreHistory{
 | 
			
		||||
		DbRestoreHistoryApp: application.GetDbRestoreHistoryApp(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqs := []*req.Conf{
 | 
			
		||||
		// 获取数据库备份历史
 | 
			
		||||
		req.NewGet(":dbId/restores/:restoreId/histories", d.GetPageList),
 | 
			
		||||
		// 删除数据库备份历史
 | 
			
		||||
		req.NewDelete(":dbId/restores/:restoreId/histories/:historyId", d.Delete),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.BatchSetGroup(dbs, reqs)
 | 
			
		||||
}
 | 
			
		||||
@@ -8,8 +8,6 @@ func Init(router *gin.RouterGroup) {
 | 
			
		||||
	InitDbSqlRouter(router)
 | 
			
		||||
	InitDbSqlExecRouter(router)
 | 
			
		||||
	InitDbBackupRouter(router)
 | 
			
		||||
	InitDbBackupHistoryRouter(router)
 | 
			
		||||
	InitDbRestoreRouter(router)
 | 
			
		||||
	InitDbRestoreHistoryRouter(router)
 | 
			
		||||
	InitDbDataSyncRouter(router)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ type MachineCronJobRelate struct {
 | 
			
		||||
	CreateTime *time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MachineCronJobRelate) SetBaseInfo(la *model.LoginAccount) {
 | 
			
		||||
func (m *MachineCronJobRelate) SetBaseInfo(gt model.IdGenType, la *model.LoginAccount) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	m.CreateTime = &now
 | 
			
		||||
	m.Creator = la.Username
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,12 @@ func (stm *SshTunnelMachine) OpenSshTunnel(id string, ip string, port int) (expo
 | 
			
		||||
	stm.mutex.Lock()
 | 
			
		||||
	defer stm.mutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	tunnel := stm.tunnels[id]
 | 
			
		||||
	// 已存在该id隧道,则直接返回
 | 
			
		||||
	if tunnel != nil {
 | 
			
		||||
		return tunnel.localHost, tunnel.localPort, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	localPort, err := netx.GetAvailablePort()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", 0, err
 | 
			
		||||
@@ -93,7 +99,7 @@ func (stm *SshTunnelMachine) OpenSshTunnel(id string, ip string, port int) (expo
 | 
			
		||||
		return "", 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tunnel := &Tunnel{
 | 
			
		||||
	tunnel = &Tunnel{
 | 
			
		||||
		id:         id,
 | 
			
		||||
		machineId:  stm.machineId,
 | 
			
		||||
		localHost:  hostname,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,11 @@ func (a *Resource) TableName() string {
 | 
			
		||||
	return "t_sys_resource"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Resource) SetBaseInfo(idGenType model.IdGenType, la *model.LoginAccount) {
 | 
			
		||||
	// id使用时间戳,减少id冲突概率
 | 
			
		||||
	m.Model.SetBaseInfo(model.IdGenTypeTimestamp, la)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ResourceStatusEnable  int8 = 1  // 启用状态
 | 
			
		||||
	ResourceStatusDisable int8 = -1 // 禁用状态
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,11 @@ package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"mayfly-go/pkg/contextx"
 | 
			
		||||
	"mayfly-go/pkg/gormx"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 基础repo接口
 | 
			
		||||
@@ -182,7 +183,7 @@ func (br *RepoImpl[T]) GetModel() T {
 | 
			
		||||
// 从上下文获取登录账号信息,并赋值至实体
 | 
			
		||||
func (br *RepoImpl[T]) setBaseInfo(ctx context.Context, e T) T {
 | 
			
		||||
	if la := contextx.GetLoginAccount(ctx); la != nil {
 | 
			
		||||
		e.SetBaseInfo(la)
 | 
			
		||||
		e.SetBaseInfo(model.IdGenTypeNone, la)
 | 
			
		||||
	}
 | 
			
		||||
	return e
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type IdGenType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	IdColumn         = "id"
 | 
			
		||||
	DeletedColumn    = "is_deleted" // 删除字段
 | 
			
		||||
@@ -11,32 +13,77 @@ const (
 | 
			
		||||
 | 
			
		||||
	ModelDeleted   int8 = 1
 | 
			
		||||
	ModelUndeleted int8 = 0
 | 
			
		||||
 | 
			
		||||
	IdGenTypeNone      IdGenType = 0 // 数据库处理
 | 
			
		||||
	IdGenTypeTimestamp IdGenType = 1 // 当前时间戳
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 实体接口
 | 
			
		||||
type ModelI interface {
 | 
			
		||||
 | 
			
		||||
	// id生成策略
 | 
			
		||||
	// IdGenType() IdGenType
 | 
			
		||||
 | 
			
		||||
	// 使用当前登录账号信息设置实体结构体的基础信息
 | 
			
		||||
	//
 | 
			
		||||
	// 如创建时间,修改时间,创建者,修改者信息
 | 
			
		||||
	SetBaseInfo(account *LoginAccount)
 | 
			
		||||
	SetBaseInfo(idGenType IdGenType, account *LoginAccount)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type IdModel struct {
 | 
			
		||||
	Id uint64 `json:"id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// func (m *IdModel) IdGenType() IdGenType {
 | 
			
		||||
// 	// 默认由数据库自行生成
 | 
			
		||||
// 	return IdGenTypeNone
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
func (m *IdModel) SetBaseInfo(idGenType IdGenType, account *LoginAccount) {
 | 
			
		||||
	// 存在id,则赋值
 | 
			
		||||
	if m.Id != 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	m.Id = GetIdByGenType(idGenType)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 含有删除字段模型
 | 
			
		||||
type DeletedModel struct {
 | 
			
		||||
	Id         uint64     `json:"id"`
 | 
			
		||||
	IdModel
 | 
			
		||||
	IsDeleted  int8       `json:"-" gorm:"column:is_deleted;default:0"`
 | 
			
		||||
	DeleteTime *time.Time `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *DeletedModel) SetBaseInfo(account *LoginAccount) {
 | 
			
		||||
	isCreate := m.Id == 0
 | 
			
		||||
	if isCreate {
 | 
			
		||||
func (m *DeletedModel) SetBaseInfo(idGenType IdGenType, account *LoginAccount) {
 | 
			
		||||
	if m.Id == 0 {
 | 
			
		||||
		m.IdModel.SetBaseInfo(idGenType, account)
 | 
			
		||||
		m.IsDeleted = ModelUndeleted
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 基础实体模型,数据表最基础字段,每张表必备字段
 | 
			
		||||
// 含有删除、创建字段模型
 | 
			
		||||
type CreateModel struct {
 | 
			
		||||
	DeletedModel
 | 
			
		||||
	CreateTime *time.Time `json:"createTime"`
 | 
			
		||||
	CreatorId  uint64     `json:"creatorId"`
 | 
			
		||||
	Creator    string     `json:"creator"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *CreateModel) SetBaseInfo(idGenType IdGenType, account *LoginAccount) {
 | 
			
		||||
	if m.Id != 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.DeletedModel.SetBaseInfo(idGenType, account)
 | 
			
		||||
	nowTime := time.Now()
 | 
			
		||||
	m.CreateTime = &nowTime
 | 
			
		||||
	if account != nil {
 | 
			
		||||
		m.CreatorId = account.Id
 | 
			
		||||
		m.Creator = account.Username
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 基础实体模型,数据表最基础字段,尽量每张表都包含这些字段
 | 
			
		||||
type Model struct {
 | 
			
		||||
	DeletedModel
 | 
			
		||||
 | 
			
		||||
@@ -49,12 +96,13 @@ type Model struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息
 | 
			
		||||
func (m *Model) SetBaseInfo(account *LoginAccount) {
 | 
			
		||||
func (m *Model) SetBaseInfo(idGenType IdGenType, account *LoginAccount) {
 | 
			
		||||
	nowTime := time.Now()
 | 
			
		||||
	isCreate := m.Id == 0
 | 
			
		||||
	if isCreate {
 | 
			
		||||
		m.IsDeleted = ModelUndeleted
 | 
			
		||||
		m.CreateTime = &nowTime
 | 
			
		||||
		m.IdModel.SetBaseInfo(idGenType, account)
 | 
			
		||||
	}
 | 
			
		||||
	m.UpdateTime = &nowTime
 | 
			
		||||
 | 
			
		||||
@@ -70,3 +118,11 @@ func (m *Model) SetBaseInfo(account *LoginAccount) {
 | 
			
		||||
	m.Modifier = name
 | 
			
		||||
	m.ModifierId = id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id生成类型,生成id
 | 
			
		||||
func GetIdByGenType(genType IdGenType) uint64 {
 | 
			
		||||
	if genType == IdGenTypeTimestamp {
 | 
			
		||||
		return uint64(time.Now().Unix())
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user