mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
feat: flow design & page query refactor
This commit is contained in:
@@ -77,7 +77,7 @@ func (d *Db) ReqConfs() *req.Confs {
|
||||
|
||||
// @router /api/dbs [get]
|
||||
func (d *Db) Dbs(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
|
||||
queryCond := req.BindQuery[*entity.DbQuery](rc, new(entity.DbQuery))
|
||||
|
||||
// 不存在可访问标签id,即没有可操作数据
|
||||
tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
@@ -85,14 +85,15 @@ func (d *Db) Dbs(rc *req.Ctx) {
|
||||
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
||||
})
|
||||
if len(tags) == 0 {
|
||||
rc.ResData = model.EmptyPageResult[any]()
|
||||
rc.ResData = model.NewEmptyPageResult[any]()
|
||||
return
|
||||
}
|
||||
queryCond.Codes = tags.GetCodes()
|
||||
|
||||
var dbvos []*vo.DbListVO
|
||||
res, err := d.dbApp.GetPageList(queryCond, page, &dbvos)
|
||||
res, err := d.dbApp.GetPageList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
resVo := model.PageResultConv[*entity.DbListPO, *vo.DbListVO](res)
|
||||
dbvos := resVo.List
|
||||
|
||||
instances, _ := d.instanceApp.GetByIds(collx.ArrayMap(dbvos, func(i *vo.DbListVO) uint64 {
|
||||
return i.InstanceId
|
||||
@@ -110,7 +111,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
rc.ResData = res
|
||||
rc.ResData = resVo
|
||||
}
|
||||
|
||||
func (d *Db) Save(rc *req.Ctx) {
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/api/form"
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DbBackup struct {
|
||||
backupApp *application.DbBackupApp `inject:"DbBackupApp"`
|
||||
dbApp application.Db `inject:"DbApp"`
|
||||
restoreApp *application.DbRestoreApp `inject:"DbRestoreApp"`
|
||||
}
|
||||
|
||||
// todo: 鉴权,避免未经授权进行数据库备份和恢复
|
||||
|
||||
// GetPageList 获取数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups [GET]
|
||||
func (d *DbBackup) GetPageList(rc *req.Ctx) {
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
||||
db, err := d.dbApp.GetById(dbId)
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbBackupQuery](rc, new(entity.DbBackupQuery))
|
||||
queryCond.DbInstanceId = db.InstanceId
|
||||
queryCond.InDbNames = strings.Fields(db.Database)
|
||||
res, err := d.backupApp.GetPageList(queryCond, page, new([]vo.DbBackup))
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库备份任务失败: %v")
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
// Create 保存数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups [POST]
|
||||
func (d *DbBackup) Create(rc *req.Ctx) {
|
||||
backupForm := req.BindJsonAndValid(rc, &form.DbBackupForm{})
|
||||
rc.ReqParam = backupForm
|
||||
|
||||
dbNames := strings.Fields(backupForm.DbNames)
|
||||
biz.IsTrue(len(dbNames) > 0, "解析数据库备份任务失败:数据库名称未定义")
|
||||
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
||||
db, err := d.dbApp.GetById(dbId)
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
jobs := make([]*entity.DbBackup, 0, len(dbNames))
|
||||
for _, dbName := range dbNames {
|
||||
job := &entity.DbBackup{
|
||||
DbInstanceId: db.InstanceId,
|
||||
DbName: dbName,
|
||||
Enabled: true,
|
||||
Repeated: backupForm.Repeated,
|
||||
StartTime: backupForm.StartTime,
|
||||
Interval: backupForm.Interval,
|
||||
Name: backupForm.Name,
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
biz.ErrIsNilAppendErr(d.backupApp.Create(rc.MetaCtx, jobs), "添加数据库备份任务失败: %v")
|
||||
}
|
||||
|
||||
// Update 保存数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups/:backupId [PUT]
|
||||
func (d *DbBackup) Update(rc *req.Ctx) {
|
||||
backupForm := &form.DbBackupForm{}
|
||||
req.BindJsonAndValid(rc, backupForm)
|
||||
rc.ReqParam = backupForm
|
||||
|
||||
job := &entity.DbBackup{}
|
||||
job.Id = backupForm.Id
|
||||
job.Name = backupForm.Name
|
||||
job.StartTime = backupForm.StartTime
|
||||
job.Interval = backupForm.Interval
|
||||
job.MaxSaveDays = backupForm.MaxSaveDays
|
||||
biz.ErrIsNilAppendErr(d.backupApp.Update(rc.MetaCtx, job), "保存数据库备份任务失败: %v")
|
||||
}
|
||||
|
||||
func (d *DbBackup) walk(rc *req.Ctx, paramName string, fn func(ctx context.Context, id uint64) error) error {
|
||||
idsStr := rc.PathParam(paramName)
|
||||
biz.NotEmpty(idsStr, paramName+" 为空")
|
||||
rc.ReqParam = idsStr
|
||||
ids := strings.Fields(idsStr)
|
||||
for _, v := range ids {
|
||||
value, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupId := uint64(value)
|
||||
err = fn(rc.MetaCtx, backupId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups/:backupId [DELETE]
|
||||
func (d *DbBackup) Delete(rc *req.Ctx) {
|
||||
err := d.walk(rc, "backupId", d.backupApp.Delete)
|
||||
biz.ErrIsNilAppendErr(err, "删除数据库备份任务失败: %v")
|
||||
}
|
||||
|
||||
// Enable 启用数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups/:backupId/enable [PUT]
|
||||
func (d *DbBackup) Enable(rc *req.Ctx) {
|
||||
err := d.walk(rc, "backupId", d.backupApp.Enable)
|
||||
biz.ErrIsNilAppendErr(err, "启用数据库备份任务失败: %v")
|
||||
}
|
||||
|
||||
// Disable 禁用数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups/:backupId/disable [PUT]
|
||||
func (d *DbBackup) Disable(rc *req.Ctx) {
|
||||
err := d.walk(rc, "backupId", d.backupApp.Disable)
|
||||
biz.ErrIsNilAppendErr(err, "禁用数据库备份任务失败: %v")
|
||||
}
|
||||
|
||||
// Start 禁用数据库备份任务
|
||||
// @router /api/dbs/:dbId/backups/:backupId/start [PUT]
|
||||
func (d *DbBackup) Start(rc *req.Ctx) {
|
||||
err := d.walk(rc, "backupId", d.backupApp.StartNow)
|
||||
biz.ErrIsNilAppendErr(err, "运行数据库备份任务失败: %v")
|
||||
}
|
||||
|
||||
// GetDbNamesWithoutBackup 获取未配置定时备份的数据库名称
|
||||
// @router /api/dbs/:dbId/db-names-without-backup [GET]
|
||||
func (d *DbBackup) GetDbNamesWithoutBackup(rc *req.Ctx) {
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
db, err := d.dbApp.GetById(dbId, "instance_id", "database")
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
dbNames := strings.Fields(db.Database)
|
||||
dbNamesWithoutBackup, err := d.backupApp.GetDbNamesWithoutBackup(db.InstanceId, dbNames)
|
||||
biz.ErrIsNilAppendErr(err, "获取未配置定时备份的数据库名称失败: %v")
|
||||
rc.ResData = dbNamesWithoutBackup
|
||||
}
|
||||
|
||||
// GetHistoryPageList 获取数据库备份历史
|
||||
// @router /api/dbs/:dbId/backups/:backupId/histories [GET]
|
||||
func (d *DbBackup) GetHistoryPageList(rc *req.Ctx) {
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
||||
db, err := d.dbApp.GetById(dbId, "instance_id", "database")
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
|
||||
backupHistoryCond, page := req.BindQueryAndPage[*entity.DbBackupHistoryQuery](rc, new(entity.DbBackupHistoryQuery))
|
||||
backupHistoryCond.DbInstanceId = db.InstanceId
|
||||
backupHistoryCond.InDbNames = strings.Fields(db.Database)
|
||||
backupHistories := make([]*vo.DbBackupHistory, 0, page.PageSize)
|
||||
res, err := d.backupApp.GetHistoryPageList(backupHistoryCond, page, &backupHistories)
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
|
||||
historyIds := make([]uint64, 0, len(backupHistories))
|
||||
for _, history := range backupHistories {
|
||||
historyIds = append(historyIds, history.Id)
|
||||
}
|
||||
restores := make([]*entity.DbRestore, 0, page.PageSize)
|
||||
if err := d.restoreApp.GetRestoresEnabled(&restores, historyIds...); err != nil {
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库备份恢复记录失败")
|
||||
}
|
||||
for _, history := range backupHistories {
|
||||
for _, restore := range restores {
|
||||
if restore.DbBackupHistoryId == history.Id {
|
||||
history.LastStatus = restore.LastStatus
|
||||
history.LastResult = restore.LastResult
|
||||
history.LastTime = restore.LastTime
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
// RestoreHistories 从数据库备份历史中恢复数据库
|
||||
// @router /api/dbs/:dbId/backup-histories/:backupHistoryId/restore [POST]
|
||||
func (d *DbBackup) RestoreHistories(rc *req.Ctx) {
|
||||
pm := rc.PathParam("backupHistoryId")
|
||||
biz.NotEmpty(pm, "backupHistoryId 为空")
|
||||
idsStr := strings.Fields(pm)
|
||||
ids := make([]uint64, 0, len(idsStr))
|
||||
for _, s := range idsStr {
|
||||
id, err := strconv.ParseUint(s, 10, 64)
|
||||
biz.ErrIsNilAppendErr(err, "从数据库备份历史恢复数据库失败: %v")
|
||||
ids = append(ids, id)
|
||||
}
|
||||
histories := make([]*entity.DbBackupHistory, 0, len(ids))
|
||||
err := d.backupApp.GetHistories(ids, &histories)
|
||||
biz.ErrIsNilAppendErr(err, "添加数据库恢复任务失败: %v")
|
||||
restores := make([]*entity.DbRestore, 0, len(histories))
|
||||
now := time.Now()
|
||||
for _, history := range histories {
|
||||
job := &entity.DbRestore{
|
||||
DbInstanceId: history.DbInstanceId,
|
||||
DbName: history.DbName,
|
||||
Enabled: true,
|
||||
Repeated: false,
|
||||
StartTime: now,
|
||||
Interval: 0,
|
||||
PointInTime: timex.NewNullTime(time.Time{}),
|
||||
DbBackupId: history.DbBackupId,
|
||||
DbBackupHistoryId: history.Id,
|
||||
DbBackupHistoryName: history.Name,
|
||||
}
|
||||
restores = append(restores, job)
|
||||
}
|
||||
biz.ErrIsNilAppendErr(d.restoreApp.Create(rc.MetaCtx, restores), "添加数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
// DeleteHistories 删除数据库备份历史
|
||||
// @router /api/dbs/:dbId/backup-histories/:backupHistoryId [DELETE]
|
||||
func (d *DbBackup) DeleteHistories(rc *req.Ctx) {
|
||||
err := d.walk(rc, "backupHistoryId", d.backupApp.DeleteHistory)
|
||||
biz.ErrIsNilAppendErr(err, "删除数据库备份历史失败: %v")
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"mayfly-go/internal/db/imsg"
|
||||
"mayfly-go/internal/pkg/utils"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"strings"
|
||||
@@ -49,17 +50,17 @@ func (d *DataSyncTask) ReqConfs() *req.Confs {
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) Tasks(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DataSyncTaskQuery](rc, new(entity.DataSyncTaskQuery))
|
||||
res, err := d.dataSyncTaskApp.GetPageList(queryCond, page, new([]vo.DataSyncTaskListVO))
|
||||
queryCond := req.BindQuery[*entity.DataSyncTaskQuery](rc, new(entity.DataSyncTaskQuery))
|
||||
res, err := d.dataSyncTaskApp.GetPageList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
rc.ResData = model.PageResultConv[*entity.DataSyncTask, *vo.DataSyncLogListVO](res)
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) Logs(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DataSyncLogQuery](rc, new(entity.DataSyncLogQuery))
|
||||
res, err := d.dataSyncTaskApp.GetTaskLogList(queryCond, page, new([]vo.DataSyncLogListVO))
|
||||
queryCond := req.BindQuery(rc, new(entity.DataSyncLogQuery))
|
||||
res, err := d.dataSyncTaskApp.GetTaskLogList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
rc.ResData = model.PageResultConv[*entity.DataSyncLog, *vo.DataSyncLogListVO](res)
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) SaveTask(rc *req.Ctx) {
|
||||
|
||||
@@ -55,7 +55,7 @@ func (d *Instance) ReqConfs() *req.Confs {
|
||||
// Instances 获取数据库实例信息
|
||||
// @router /api/instances [get]
|
||||
func (d *Instance) Instances(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery))
|
||||
queryCond := req.BindQuery(rc, new(entity.InstanceQuery))
|
||||
|
||||
tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert)),
|
||||
@@ -63,7 +63,7 @@ func (d *Instance) Instances(rc *req.Ctx) {
|
||||
})
|
||||
// 不存在可操作的数据库,即没有可操作数据
|
||||
if len(tags) == 0 {
|
||||
rc.ResData = model.EmptyPageResult[any]()
|
||||
rc.ResData = model.NewEmptyPageResult[any]()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,9 +71,10 @@ func (d *Instance) Instances(rc *req.Ctx) {
|
||||
dbInstCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeDbInstance, tagCodePaths...)
|
||||
queryCond.Codes = dbInstCodes
|
||||
|
||||
var instvos []*vo.InstanceListVO
|
||||
res, err := d.instanceApp.GetPageList(queryCond, page, &instvos)
|
||||
res, err := d.instanceApp.GetPageList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
resVo := model.PageResultConv[*entity.DbInstance, *vo.InstanceListVO](res)
|
||||
instvos := resVo.List
|
||||
|
||||
// 填充授权凭证信息
|
||||
d.resourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
|
||||
@@ -85,7 +86,7 @@ func (d *Instance) Instances(rc *req.Ctx) {
|
||||
return insvo
|
||||
})...)
|
||||
|
||||
rc.ResData = res
|
||||
rc.ResData = resVo
|
||||
}
|
||||
|
||||
func (d *Instance) TestConn(rc *req.Ctx) {
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/api/form"
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DbRestore struct {
|
||||
restoreApp *application.DbRestoreApp `inject:"DbRestoreApp"`
|
||||
dbApp application.Db `inject:"DbApp"`
|
||||
}
|
||||
|
||||
// GetPageList 获取数据库恢复任务
|
||||
// @router /api/dbs/:dbId/restores [GET]
|
||||
func (d *DbRestore) GetPageList(rc *req.Ctx) {
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
||||
db, err := d.dbApp.GetById(dbId, "db_instance_id", "database")
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
|
||||
var restores []vo.DbRestore
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbRestoreQuery](rc, new(entity.DbRestoreQuery))
|
||||
queryCond.DbInstanceId = db.InstanceId
|
||||
queryCond.InDbNames = strings.Fields(db.Database)
|
||||
res, err := d.restoreApp.GetPageList(queryCond, page, &restores)
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库恢复任务失败: %v")
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
// Create 保存数据库恢复任务
|
||||
// @router /api/dbs/:dbId/restores [POST]
|
||||
func (d *DbRestore) Create(rc *req.Ctx) {
|
||||
restoreForm := &form.DbRestoreForm{}
|
||||
req.BindJsonAndValid(rc, restoreForm)
|
||||
rc.ReqParam = restoreForm
|
||||
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
||||
db, err := d.dbApp.GetById(dbId, "instanceId")
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
|
||||
job := &entity.DbRestore{
|
||||
DbInstanceId: db.InstanceId,
|
||||
DbName: restoreForm.DbName,
|
||||
Enabled: true,
|
||||
Repeated: restoreForm.Repeated,
|
||||
StartTime: restoreForm.StartTime,
|
||||
Interval: restoreForm.Interval,
|
||||
PointInTime: restoreForm.PointInTime,
|
||||
DbBackupId: restoreForm.DbBackupId,
|
||||
DbBackupHistoryId: restoreForm.DbBackupHistoryId,
|
||||
DbBackupHistoryName: restoreForm.DbBackupHistoryName,
|
||||
}
|
||||
biz.ErrIsNilAppendErr(d.restoreApp.Create(rc.MetaCtx, job), "添加数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
func (d *DbRestore) createWithBackupHistory(backupHistoryIds string) {
|
||||
|
||||
}
|
||||
|
||||
// Update 保存数据库恢复任务
|
||||
// @router /api/dbs/:dbId/restores/:restoreId [PUT]
|
||||
func (d *DbRestore) Update(rc *req.Ctx) {
|
||||
restoreForm := &form.DbRestoreForm{}
|
||||
req.BindJsonAndValid(rc, restoreForm)
|
||||
rc.ReqParam = restoreForm
|
||||
|
||||
job := &entity.DbRestore{}
|
||||
job.Id = restoreForm.Id
|
||||
job.StartTime = restoreForm.StartTime
|
||||
job.Interval = restoreForm.Interval
|
||||
biz.ErrIsNilAppendErr(d.restoreApp.Update(rc.MetaCtx, job), "保存数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
func (d *DbRestore) walk(rc *req.Ctx, fn func(ctx context.Context, restoreId uint64) error) error {
|
||||
idsStr := rc.PathParam("restoreId")
|
||||
biz.NotEmpty(idsStr, "restoreId 为空")
|
||||
rc.ReqParam = idsStr
|
||||
ids := strings.Fields(idsStr)
|
||||
for _, v := range ids {
|
||||
value, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
restoreId := uint64(value)
|
||||
err = fn(rc.MetaCtx, restoreId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除数据库恢复任务
|
||||
// @router /api/dbs/:dbId/restores/:restoreId [DELETE]
|
||||
func (d *DbRestore) Delete(rc *req.Ctx) {
|
||||
err := d.walk(rc, d.restoreApp.Delete)
|
||||
biz.ErrIsNilAppendErr(err, "删除数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
// Enable 启用数据库恢复任务
|
||||
// @router /api/dbs/:dbId/restores/:restoreId/enable [PUT]
|
||||
func (d *DbRestore) Enable(rc *req.Ctx) {
|
||||
err := d.walk(rc, d.restoreApp.Enable)
|
||||
biz.ErrIsNilAppendErr(err, "启用数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
// Disable 禁用数据库恢复任务
|
||||
// @router /api/dbs/:dbId/restores/:restoreId/disable [PUT]
|
||||
func (d *DbRestore) Disable(rc *req.Ctx) {
|
||||
err := d.walk(rc, d.restoreApp.Disable)
|
||||
biz.ErrIsNilAppendErr(err, "禁用数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
// GetDbNamesWithoutRestore 获取未配置定时恢复的数据库名称
|
||||
// @router /api/dbs/:dbId/db-names-without-backup [GET]
|
||||
func (d *DbRestore) GetDbNamesWithoutRestore(rc *req.Ctx) {
|
||||
dbId := uint64(rc.PathParamInt("dbId"))
|
||||
db, err := d.dbApp.GetById(dbId, "instance_id", "database")
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
dbNames := strings.Fields(db.Database)
|
||||
dbNamesWithoutRestore, err := d.restoreApp.GetDbNamesWithoutRestore(db.InstanceId, dbNames)
|
||||
biz.ErrIsNilAppendErr(err, "获取未配置定时备份的数据库名称失败: %v")
|
||||
rc.ResData = dbNamesWithoutRestore
|
||||
}
|
||||
|
||||
// GetHistoryPageList 获取数据库备份历史
|
||||
// @router /api/dbs/:dbId/restores/:restoreId/histories [GET]
|
||||
func (d *DbRestore) GetHistoryPageList(rc *req.Ctx) {
|
||||
queryCond := &entity.DbRestoreHistoryQuery{
|
||||
DbRestoreId: uint64(rc.PathParamInt("restoreId")),
|
||||
}
|
||||
res, err := d.restoreApp.GetHistoryPageList(queryCond, rc.GetPageParam(), new([]vo.DbRestoreHistory))
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
|
||||
rc.ResData = res
|
||||
}
|
||||
@@ -26,14 +26,13 @@ func (d *DbSqlExec) ReqConfs() *req.Confs {
|
||||
}
|
||||
|
||||
func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage(rc, new(entity.DbSqlExecQuery))
|
||||
|
||||
queryCond := req.BindQuery(rc, new(entity.DbSqlExecQuery))
|
||||
if statusStr := rc.Query("status"); statusStr != "" {
|
||||
queryCond.Status = collx.ArrayMap[string, int8](strings.Split(statusStr, ","), func(val string) int8 {
|
||||
return cast.ToInt8(val)
|
||||
})
|
||||
}
|
||||
res, err := d.dbSqlExecApp.GetPageList(queryCond, page, new([]entity.DbSqlExec))
|
||||
res, err := d.dbSqlExecApp.GetPageList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
@@ -60,21 +61,20 @@ func (d *DbTransferTask) ReqConfs() *req.Confs {
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbTransferTaskQuery](rc, new(entity.DbTransferTaskQuery))
|
||||
res, err := d.dbTransferTask.GetPageList(queryCond, page, new([]vo.DbTransferTaskListVO))
|
||||
biz.ErrIsNil(err)
|
||||
queryCond := req.BindQuery(rc, new(entity.DbTransferTaskQuery))
|
||||
|
||||
if res.List != nil {
|
||||
list := res.List.(*[]vo.DbTransferTaskListVO)
|
||||
for _, item := range *list {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.dbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
}
|
||||
res, err := d.dbTransferTask.GetPageList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
resVo := model.PageResultConv[*entity.DbTransferTask, *vo.DbTransferTaskListVO](res)
|
||||
|
||||
for _, item := range resVo.List {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.dbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
}
|
||||
}
|
||||
|
||||
rc.ResData = res
|
||||
rc.ResData = resVo
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) SaveTask(rc *req.Ctx) {
|
||||
@@ -122,8 +122,9 @@ func (d *DbTransferTask) Stop(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Files(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.DbTransferFileQuery](rc, new(entity.DbTransferFileQuery))
|
||||
res, err := d.dbTransferFile.GetPageList(queryCond, page, new([]vo.DbTransferFileListVO))
|
||||
queryCond := req.BindQuery(rc, new(entity.DbTransferFileQuery))
|
||||
|
||||
res, err := d.dbTransferFile.GetPageList(queryCond)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DbBackup 数据库备份任务
|
||||
type DbBackup struct {
|
||||
Id uint64 `json:"id"`
|
||||
DbName string `json:"dbName"` // 数据库名
|
||||
CreateTime time.Time `json:"createTime"` // 创建时间
|
||||
StartTime time.Time `json:"startTime"` // 开始时间
|
||||
Interval time.Duration `json:"-"` // 间隔时间
|
||||
IntervalDay uint64 `json:"intervalDay" gorm:"-"` // 间隔天数
|
||||
MaxSaveDays int `json:"maxSaveDays"` // 数据库备份历史保留天数,过期将自动删除
|
||||
Enabled bool `json:"enabled"` // 是否启用
|
||||
EnabledDesc string `json:"enabledDesc"` // 启用状态描述
|
||||
LastTime timex.NullTime `json:"lastTime"` // 最近一次执行时间
|
||||
LastStatus entity.DbJobStatus `json:"lastStatus"` // 最近一次执行状态
|
||||
LastResult string `json:"lastResult"` // 最近一次执行结果
|
||||
DbInstanceId uint64 `json:"dbInstanceId"` // 数据库实例ID
|
||||
Name string `json:"name"` // 备份任务名称
|
||||
}
|
||||
|
||||
func (backup *DbBackup) MarshalJSON() ([]byte, error) {
|
||||
type dbBackup DbBackup
|
||||
backup.IntervalDay = uint64(backup.Interval / time.Hour / 24)
|
||||
if len(backup.EnabledDesc) == 0 {
|
||||
if backup.Enabled {
|
||||
backup.EnabledDesc = "已启用"
|
||||
} else {
|
||||
backup.EnabledDesc = "已禁用"
|
||||
}
|
||||
}
|
||||
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"` // 备份历史名称
|
||||
BinlogFileName string `json:"binlogFileName"`
|
||||
LastTime timex.NullTime `json:"lastTime" gorm:"-"` // 最近一次恢复时间
|
||||
LastStatus entity.DbJobStatus `json:"lastStatus" gorm:"-"` // 最近一次恢复状态
|
||||
LastResult string `json:"lastResult" gorm:"-"` // 最近一次恢复结果
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DbRestore 数据库备份任务
|
||||
type DbRestore struct {
|
||||
Id uint64 `json:"id"`
|
||||
DbName string `json:"dbName"` // 数据库名
|
||||
StartTime time.Time `json:"startTime"` // 开始时间
|
||||
Interval time.Duration `json:"-"` // 间隔时间
|
||||
IntervalDay uint64 `json:"intervalDay" gorm:"-"` // 间隔天数
|
||||
Enabled bool `json:"enabled"` // 是否启用
|
||||
EnabledDesc string `json:"enabledDesc"` // 启用状态描述
|
||||
LastTime timex.NullTime `json:"lastTime"` // 最近一次执行时间
|
||||
LastStatus string `json:"lastStatus"` // 最近一次执行状态
|
||||
LastResult string `json:"lastResult"` // 最近一次执行结果
|
||||
PointInTime timex.NullTime `json:"pointInTime"` // 指定数据库恢复的时间点
|
||||
DbBackupId uint64 `json:"dbBackupId"` // 数据库备份任务ID
|
||||
DbBackupHistoryId uint64 `json:"dbBackupHistoryId"` // 数据库备份历史ID
|
||||
DbBackupHistoryName string `json:"dbBackupHistoryName"` // 数据库备份历史名称
|
||||
DbInstanceId uint64 `json:"dbInstanceId"` // 数据库实例ID
|
||||
}
|
||||
|
||||
func (restore *DbRestore) MarshalJSON() ([]byte, error) {
|
||||
type dbBackup DbRestore
|
||||
restore.IntervalDay = uint64(restore.Interval / time.Hour / 24)
|
||||
if len(restore.EnabledDesc) == 0 {
|
||||
if restore.Enabled {
|
||||
restore.EnabledDesc = "已启用"
|
||||
} else {
|
||||
restore.EnabledDesc = "已禁用"
|
||||
}
|
||||
}
|
||||
return json.Marshal((*dbBackup)(restore))
|
||||
}
|
||||
|
||||
// DbRestoreHistory 数据库备份历史
|
||||
type DbRestoreHistory struct {
|
||||
Id uint64 `json:"id"`
|
||||
DbRestoreId uint64 `json:"dbRestoreId"`
|
||||
}
|
||||
@@ -13,11 +13,6 @@ func InitIoc() {
|
||||
ioc.Register(new(dataSyncAppImpl), ioc.WithComponentName("DbDataSyncTaskApp"))
|
||||
ioc.Register(new(dbTransferAppImpl), ioc.WithComponentName("DbTransferTaskApp"))
|
||||
ioc.Register(new(dbTransferFileAppImpl), ioc.WithComponentName("DbTransferFileApp"))
|
||||
|
||||
ioc.Register(newDbScheduler(), ioc.WithComponentName("DbScheduler"))
|
||||
ioc.Register(new(DbBackupApp), ioc.WithComponentName("DbBackupApp"))
|
||||
ioc.Register(new(DbRestoreApp), ioc.WithComponentName("DbRestoreApp"))
|
||||
ioc.Register(newDbBinlogApp(), ioc.WithComponentName("DbBinlogApp"))
|
||||
}
|
||||
|
||||
func Init() {
|
||||
@@ -43,18 +38,6 @@ func GetDbSqlExecApp() DbSqlExec {
|
||||
return ioc.Get[DbSqlExec]("DbSqlExecApp")
|
||||
}
|
||||
|
||||
func GetDbBackupApp() *DbBackupApp {
|
||||
return ioc.Get[*DbBackupApp]("DbBackupApp")
|
||||
}
|
||||
|
||||
func GetDbRestoreApp() *DbRestoreApp {
|
||||
return ioc.Get[*DbRestoreApp]("DbRestoreApp")
|
||||
}
|
||||
|
||||
func GetDbBinlogApp() *DbBinlogApp {
|
||||
return ioc.Get[*DbBinlogApp]("DbBinlogApp")
|
||||
}
|
||||
|
||||
func GetDataSyncTaskApp() DataSyncTask {
|
||||
return ioc.Get[DataSyncTask]("DbDataSyncTaskApp")
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type Db interface {
|
||||
base.App[*entity.Db]
|
||||
|
||||
// 分页获取
|
||||
GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error)
|
||||
|
||||
SaveDb(ctx context.Context, entity *entity.Db) error
|
||||
|
||||
@@ -62,8 +62,8 @@ type dbAppImpl struct {
|
||||
var _ (Db) = (*dbAppImpl)(nil)
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return d.GetRepo().GetDbList(condition, pageParam, toEntity, orderBy...)
|
||||
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error) {
|
||||
return d.GetRepo().GetDbList(condition, orderBy...)
|
||||
}
|
||||
|
||||
func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const maxBackupHistoryDays = 30
|
||||
|
||||
var (
|
||||
errRestoringBackupHistory = errors.New("正在从备份历史中恢复数据库")
|
||||
)
|
||||
|
||||
type DbBackupApp struct {
|
||||
scheduler *dbScheduler `inject:"DbScheduler"`
|
||||
backupRepo repository.DbBackup `inject:"DbBackupRepo"`
|
||||
backupHistoryRepo repository.DbBackupHistory `inject:"DbBackupHistoryRepo"`
|
||||
restoreRepo repository.DbRestore `inject:"DbRestoreRepo"`
|
||||
dbApp Db `inject:"DbApp"`
|
||||
mutex sync.Mutex
|
||||
closed chan struct{}
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Init() error {
|
||||
var jobs []*entity.DbBackup
|
||||
if err := app.backupRepo.ListToDo(&jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := app.scheduler.AddJob(context.Background(), jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
app.ctx, app.cancel = context.WithCancel(context.Background())
|
||||
app.wg.Add(1)
|
||||
go func() {
|
||||
defer app.wg.Done()
|
||||
for app.ctx.Err() == nil {
|
||||
if err := app.prune(app.ctx); err != nil {
|
||||
logx.Errorf("清理数据库备份历史失败: %s", err.Error())
|
||||
timex.SleepWithContext(app.ctx, time.Minute*15)
|
||||
continue
|
||||
}
|
||||
timex.SleepWithContext(app.ctx, time.Hour*24)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) prune(ctx context.Context) error {
|
||||
jobs, err := app.backupRepo.SelectByCond(map[string]any{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, job := range jobs {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
historyCond := map[string]any{
|
||||
"db_backup_id": job.Id,
|
||||
}
|
||||
histories, _ := app.backupHistoryRepo.SelectByCond(historyCond)
|
||||
expiringTime := time.Now().Add(-math.MaxInt64)
|
||||
if job.MaxSaveDays > 0 {
|
||||
expiringTime = time.Now().Add(-time.Hour * 24 * time.Duration(job.MaxSaveDays+1))
|
||||
}
|
||||
for _, history := range histories {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
if history.CreateTime.After(expiringTime) {
|
||||
break
|
||||
}
|
||||
err := app.DeleteHistory(ctx, history.Id)
|
||||
if errors.Is(err, errRestoringBackupHistory) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Close() {
|
||||
app.scheduler.Close()
|
||||
if app.cancel != nil {
|
||||
app.cancel()
|
||||
app.cancel = nil
|
||||
}
|
||||
app.wg.Wait()
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Create(ctx context.Context, jobs []*entity.DbBackup) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if err := app.backupRepo.AddJob(ctx, jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
return app.scheduler.AddJob(ctx, jobs)
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Update(ctx context.Context, job *entity.DbBackup) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if err := app.backupRepo.UpdateById(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = app.scheduler.UpdateJob(ctx, job)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Delete(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if err := app.scheduler.RemoveJob(ctx, entity.DbJobTypeBackup, jobId); err != nil {
|
||||
return err
|
||||
}
|
||||
history := &entity.DbBackupHistory{
|
||||
DbBackupId: jobId,
|
||||
}
|
||||
err := app.backupHistoryRepo.GetByCond(history)
|
||||
switch {
|
||||
default:
|
||||
return err
|
||||
case err == nil:
|
||||
return fmt.Errorf("请先删除关联的数据库备份历史【%s】", history.Name)
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
}
|
||||
if err := app.backupRepo.DeleteById(ctx, jobId); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Enable(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
repo := app.backupRepo
|
||||
job, err := repo.GetById(jobId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
if job.IsExpired() {
|
||||
return errors.New("任务已过期")
|
||||
}
|
||||
_ = app.scheduler.EnableJob(ctx, job)
|
||||
if err := repo.UpdateEnabled(ctx, jobId, true); err != nil {
|
||||
logx.Errorf("数据库备份任务已启用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) Disable(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
repo := app.backupRepo
|
||||
job, err := repo.GetById(jobId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !job.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
_ = app.scheduler.DisableJob(ctx, entity.DbJobTypeBackup, jobId)
|
||||
if err := repo.UpdateEnabled(ctx, jobId, false); err != nil {
|
||||
logx.Errorf("数据库恢复任务已禁用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) StartNow(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
job, err := app.backupRepo.GetById(jobId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !job.IsEnabled() {
|
||||
return errors.New("任务未启用")
|
||||
}
|
||||
_ = app.scheduler.StartJobNow(ctx, job)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageList 分页获取数据库备份任务
|
||||
func (app *DbBackupApp) GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.backupRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// GetDbNamesWithoutBackup 获取未配置定时备份的数据库名称
|
||||
func (app *DbBackupApp) GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error) {
|
||||
return app.backupRepo.GetDbNamesWithoutBackup(instanceId, dbNames)
|
||||
}
|
||||
|
||||
// GetHistoryPageList 分页获取数据库备份历史
|
||||
func (app *DbBackupApp) GetHistoryPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.backupHistoryRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) GetHistories(backupHistoryIds []uint64, toEntity any) error {
|
||||
return app.backupHistoryRepo.GetHistories(backupHistoryIds, toEntity)
|
||||
}
|
||||
|
||||
func NewIncUUID() (uuid.UUID, error) {
|
||||
var uid uuid.UUID
|
||||
now, seq, err := uuid.GetTime()
|
||||
if err != nil {
|
||||
return uid, err
|
||||
}
|
||||
timeHi := uint32((now >> 28) & 0xffffffff)
|
||||
timeMid := uint16((now >> 12) & 0xffff)
|
||||
timeLow := uint16(now & 0x0fff)
|
||||
timeLow |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uid[0:], timeHi)
|
||||
binary.BigEndian.PutUint16(uid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uid[6:], timeLow)
|
||||
binary.BigEndian.PutUint16(uid[8:], seq)
|
||||
|
||||
copy(uid[10:], uuid.NodeID())
|
||||
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
func (app *DbBackupApp) DeleteHistory(ctx context.Context, historyId uint64) (retErr error) {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if _, err := app.backupHistoryRepo.UpdateDeleting(false, historyId); err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := app.backupHistoryRepo.UpdateDeleting(true, historyId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return errRestoringBackupHistory
|
||||
}
|
||||
job, err := app.backupHistoryRepo.GetById(historyId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := app.dbApp.GetDbConnByInstanceId(job.DbInstanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbProgram, err := conn.GetDialect().GetDbProgram()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dbProgram.RemoveBackupHistory(ctx, job.DbBackupId, job.Uuid); err != nil {
|
||||
return err
|
||||
}
|
||||
return app.backupHistoryRepo.DeleteById(ctx, historyId)
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DbBinlogApp struct {
|
||||
scheduler *dbScheduler `inject:"DbScheduler"`
|
||||
binlogRepo repository.DbBinlog `inject:"DbBinlogRepo"`
|
||||
binlogHistoryRepo repository.DbBinlogHistory `inject:"DbBinlogHistoryRepo"`
|
||||
backupRepo repository.DbBackup `inject:"DbBackupRepo"`
|
||||
backupHistoryRepo repository.DbBackupHistory `inject:"DbBackupHistoryRepo"`
|
||||
instanceRepo repository.Instance `inject:"DbInstanceRepo"`
|
||||
dbApp Db `inject:"DbApp"`
|
||||
|
||||
context context.Context
|
||||
cancel context.CancelFunc
|
||||
waitGroup sync.WaitGroup
|
||||
}
|
||||
|
||||
func newDbBinlogApp() *DbBinlogApp {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
svc := &DbBinlogApp{
|
||||
context: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) Init() error {
|
||||
app.context, app.cancel = context.WithCancel(context.Background())
|
||||
app.waitGroup.Add(1)
|
||||
go app.run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) run() {
|
||||
defer app.waitGroup.Done()
|
||||
|
||||
for app.context.Err() == nil {
|
||||
if err := app.fetchBinlog(app.context); err != nil {
|
||||
timex.SleepWithContext(app.context, time.Minute)
|
||||
continue
|
||||
}
|
||||
if err := app.pruneBinlog(app.context); err != nil {
|
||||
timex.SleepWithContext(app.context, time.Minute)
|
||||
continue
|
||||
}
|
||||
timex.SleepWithContext(app.context, entity.BinlogDownloadInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) fetchBinlog(ctx context.Context) error {
|
||||
jobs, err := app.loadJobs(ctx)
|
||||
if err != nil {
|
||||
logx.Errorf("DbBinlogApp: 加载 BINLOG 同步任务失败: %s", err.Error())
|
||||
timex.SleepWithContext(app.context, time.Minute)
|
||||
return err
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if err := app.scheduler.AddJob(app.context, jobs); err != nil {
|
||||
logx.Error("DbBinlogApp: 添加 BINLOG 同步任务失败: ", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) pruneBinlog(ctx context.Context) error {
|
||||
jobs, err := app.binlogRepo.SelectByCond(map[string]any{})
|
||||
if err != nil {
|
||||
logx.Error("DbBinlogApp: 获取 BINLOG 同步任务失败: ", err.Error())
|
||||
return err
|
||||
}
|
||||
for _, instance := range jobs {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
var histories []*entity.DbBinlogHistory
|
||||
backupHistory, backupHistoryExists, err := app.backupHistoryRepo.GetEarliestHistoryForBinlog(instance.Id)
|
||||
if err != nil {
|
||||
logx.Errorf("DbBinlogApp: 获取数据库备份历史失败: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
var binlogSeq int64 = math.MaxInt64
|
||||
if backupHistoryExists {
|
||||
binlogSeq = backupHistory.BinlogSequence
|
||||
}
|
||||
if err := app.binlogHistoryRepo.GetHistoriesBeforeSequence(ctx, instance.Id, binlogSeq, &histories); err != nil {
|
||||
logx.Errorf("DbBinlogApp: 获取数据库 BINLOG 历史失败: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
conn, err := app.dbApp.GetDbConnByInstanceId(instance.Id)
|
||||
if err != nil {
|
||||
logx.Errorf("DbBinlogApp: 创建数据库连接失败: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
dbProgram, err := conn.GetDialect().GetDbProgram()
|
||||
if err != nil {
|
||||
logx.Errorf("DbBinlogApp: 获取数据库备份与恢复程序失败: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
for i, history := range histories {
|
||||
// todo: 在避免并发访问的前提下删除本地最新的 BINLOG 文件
|
||||
if !backupHistoryExists && i == len(histories)-1 {
|
||||
// 暂不删除本地最新的 BINLOG 文件
|
||||
break
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if err := dbProgram.PruneBinlog(history); err != nil {
|
||||
logx.Errorf("清理 BINLOG 文件失败: %v", err)
|
||||
continue
|
||||
}
|
||||
if err := app.binlogHistoryRepo.DeleteById(ctx, history.Id); err != nil {
|
||||
logx.Errorf("删除 BINLOG 历史失败: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) loadJobs(ctx context.Context) ([]*entity.DbBinlog, error) {
|
||||
var instanceIds []uint64
|
||||
if err := app.backupRepo.ListDbInstances(true, true, &instanceIds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobs := make([]*entity.DbBinlog, 0, len(instanceIds))
|
||||
for _, id := range instanceIds {
|
||||
if ctx.Err() != nil {
|
||||
break
|
||||
}
|
||||
binlog := entity.NewDbBinlog(id)
|
||||
if err := app.AddJobIfNotExists(app.context, binlog); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobs = append(jobs, binlog)
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) Close() {
|
||||
cancel := app.cancel
|
||||
if cancel == nil {
|
||||
return
|
||||
}
|
||||
app.cancel = nil
|
||||
cancel()
|
||||
app.waitGroup.Wait()
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) AddJobIfNotExists(ctx context.Context, job *entity.DbBinlog) error {
|
||||
if err := app.binlogRepo.AddJobIfNotExists(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
if job.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -28,7 +28,7 @@ type DataSyncTask interface {
|
||||
base.App[*entity.DataSyncTask]
|
||||
|
||||
// GetPageList 分页获取数据库实例
|
||||
GetPageList(condition *entity.DataSyncTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DataSyncTaskQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncTask], error)
|
||||
|
||||
Save(ctx context.Context, instanceEntity *entity.DataSyncTask) error
|
||||
|
||||
@@ -44,7 +44,7 @@ type DataSyncTask interface {
|
||||
|
||||
StopTask(ctx context.Context, id uint64) error
|
||||
|
||||
GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetTaskLogList(condition *entity.DataSyncLogQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncLog], error)
|
||||
}
|
||||
|
||||
var _ (DataSyncTask) = (*dataSyncAppImpl)(nil)
|
||||
@@ -65,8 +65,8 @@ func (app *dataSyncAppImpl) InjectDbDataSyncTaskRepo(repo repository.DataSyncTas
|
||||
app.Repo = repo
|
||||
}
|
||||
|
||||
func (app *dataSyncAppImpl) GetPageList(condition *entity.DataSyncTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.GetRepo().GetTaskList(condition, pageParam, toEntity, orderBy...)
|
||||
func (app *dataSyncAppImpl) GetPageList(condition *entity.DataSyncTaskQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncTask], error) {
|
||||
return app.GetRepo().GetTaskList(condition, orderBy...)
|
||||
}
|
||||
|
||||
func (app *dataSyncAppImpl) Save(ctx context.Context, taskEntity *entity.DataSyncTask) error {
|
||||
@@ -406,39 +406,36 @@ func (app *dataSyncAppImpl) InitCronJob() {
|
||||
_ = app.UpdateByCond(context.TODO(), &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateReady}, &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateRunning})
|
||||
|
||||
// 把所有正常任务添加到定时任务中
|
||||
pageParam := &model.PageParam{
|
||||
PageSize: 100,
|
||||
PageNum: 1,
|
||||
}
|
||||
cond := new(entity.DataSyncTaskQuery)
|
||||
cond.PageNum = 1
|
||||
cond.PageSize = 100
|
||||
cond.Status = entity.DataSyncTaskStatusEnable
|
||||
jobs := new([]entity.DataSyncTask)
|
||||
|
||||
pr, err := app.GetPageList(cond, pageParam, jobs)
|
||||
tasks, err := app.GetPageList(cond)
|
||||
if err != nil {
|
||||
logx.ErrorTrace("the data synchronization task failed to initialize", err)
|
||||
return
|
||||
}
|
||||
|
||||
total := pr.Total
|
||||
total := tasks.Total
|
||||
add := 0
|
||||
|
||||
for {
|
||||
for _, job := range *jobs {
|
||||
app.AddCronJob(contextx.NewTraceId(), &job)
|
||||
for _, job := range tasks.List {
|
||||
app.AddCronJob(contextx.NewTraceId(), job)
|
||||
add++
|
||||
}
|
||||
if add >= int(total) {
|
||||
return
|
||||
}
|
||||
|
||||
pageParam.PageNum++
|
||||
_, _ = app.GetPageList(cond, pageParam, jobs)
|
||||
cond.PageNum = cond.PageNum + 1
|
||||
tasks, _ = app.GetPageList(cond)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *dataSyncAppImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.dbDataSyncLogRepo.GetTaskLogList(condition, pageParam, toEntity, orderBy...)
|
||||
func (app *dataSyncAppImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncLog], error) {
|
||||
return app.dbDataSyncLogRepo.GetTaskLogList(condition, orderBy...)
|
||||
}
|
||||
|
||||
// MarkRunning 标记任务执行中
|
||||
|
||||
@@ -25,7 +25,7 @@ type Instance interface {
|
||||
base.App[*entity.DbInstance]
|
||||
|
||||
// GetPageList 分页获取数据库实例
|
||||
GetPageList(condition *entity.InstanceQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.InstanceQuery, orderBy ...string) (*model.PageResult[*entity.DbInstance], error)
|
||||
|
||||
TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error
|
||||
|
||||
@@ -55,8 +55,8 @@ type instanceAppImpl struct {
|
||||
var _ (Instance) = (*instanceAppImpl)(nil)
|
||||
|
||||
// GetPageList 分页获取数据库实例
|
||||
func (app *instanceAppImpl) GetPageList(condition *entity.InstanceQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.GetRepo().GetInstanceList(condition, pageParam, toEntity, orderBy...)
|
||||
func (app *instanceAppImpl) GetPageList(condition *entity.InstanceQuery, orderBy ...string) (*model.PageResult[*entity.DbInstance], error) {
|
||||
return app.GetRepo().GetInstanceList(condition, orderBy...)
|
||||
}
|
||||
|
||||
func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error {
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type DbRestoreApp struct {
|
||||
scheduler *dbScheduler `inject:"DbScheduler"`
|
||||
restoreRepo repository.DbRestore `inject:"DbRestoreRepo"`
|
||||
restoreHistoryRepo repository.DbRestoreHistory `inject:"DbRestoreHistoryRepo"`
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Init() error {
|
||||
var jobs []*entity.DbRestore
|
||||
if err := app.restoreRepo.ListToDo(&jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := app.scheduler.AddJob(context.Background(), jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Close() {
|
||||
app.scheduler.Close()
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Create(ctx context.Context, jobs any) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if err := app.restoreRepo.AddJob(ctx, jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = app.scheduler.AddJob(ctx, jobs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Update(ctx context.Context, job *entity.DbRestore) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if err := app.restoreRepo.UpdateById(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = app.scheduler.UpdateJob(ctx, job)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Delete(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
if err := app.scheduler.RemoveJob(ctx, entity.DbJobTypeRestore, jobId); err != nil {
|
||||
return err
|
||||
}
|
||||
history := &entity.DbRestoreHistory{
|
||||
DbRestoreId: jobId,
|
||||
}
|
||||
if err := app.restoreHistoryRepo.DeleteByCond(ctx, history); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := app.restoreRepo.DeleteById(ctx, jobId); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Enable(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
repo := app.restoreRepo
|
||||
job, err := repo.GetById(jobId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
if job.IsExpired() {
|
||||
return errors.New("任务已过期")
|
||||
}
|
||||
_ = app.scheduler.EnableJob(ctx, job)
|
||||
if err := repo.UpdateEnabled(ctx, jobId, true); err != nil {
|
||||
logx.Errorf("数据库恢复任务已启用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbRestoreApp) Disable(ctx context.Context, jobId uint64) error {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
repo := app.restoreRepo
|
||||
job, err := repo.GetById(jobId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !job.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
_ = app.scheduler.DisableJob(ctx, entity.DbJobTypeRestore, jobId)
|
||||
if err := repo.UpdateEnabled(ctx, jobId, false); err != nil {
|
||||
logx.Errorf("数据库恢复任务已禁用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageList 分页获取数据库恢复任务
|
||||
func (app *DbRestoreApp) GetPageList(condition *entity.DbRestoreQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.restoreRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// GetRestoresEnabled 获取数据库恢复任务
|
||||
func (app *DbRestoreApp) GetRestoresEnabled(toEntity any, backupHistoryId ...uint64) error {
|
||||
return app.restoreRepo.GetEnabledRestores(toEntity, backupHistoryId...)
|
||||
}
|
||||
|
||||
// GetDbNamesWithoutRestore 获取未配置定时恢复的数据库名称
|
||||
func (app *DbRestoreApp) GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error) {
|
||||
return app.restoreRepo.GetDbNamesWithoutRestore(instanceId, dbNames)
|
||||
}
|
||||
|
||||
// GetHistoryPageList 分页获取数据库备份历史
|
||||
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...)
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/runner"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRunning = 8
|
||||
)
|
||||
|
||||
type dbScheduler struct {
|
||||
mutex sync.Mutex
|
||||
runner *runner.Runner[entity.DbJob]
|
||||
dbApp Db `inject:"DbApp"`
|
||||
backupRepo repository.DbBackup `inject:"DbBackupRepo"`
|
||||
backupHistoryRepo repository.DbBackupHistory `inject:"DbBackupHistoryRepo"`
|
||||
restoreRepo repository.DbRestore `inject:"DbRestoreRepo"`
|
||||
restoreHistoryRepo repository.DbRestoreHistory `inject:"DbRestoreHistoryRepo"`
|
||||
binlogRepo repository.DbBinlog `inject:"DbBinlogRepo"`
|
||||
binlogHistoryRepo repository.DbBinlogHistory `inject:"DbBinlogHistoryRepo"`
|
||||
sfGroup singleflight.Group
|
||||
}
|
||||
|
||||
func newDbScheduler() *dbScheduler {
|
||||
scheduler := &dbScheduler{}
|
||||
scheduler.runner = runner.NewRunner[entity.DbJob](maxRunning, scheduler.runJob,
|
||||
runner.WithScheduleJob[entity.DbJob](scheduler.scheduleJob),
|
||||
runner.WithRunnableJob[entity.DbJob](scheduler.runnableJob),
|
||||
runner.WithUpdateJob[entity.DbJob](scheduler.updateJob),
|
||||
)
|
||||
return scheduler
|
||||
}
|
||||
|
||||
func (s *dbScheduler) scheduleJob(job entity.DbJob) (time.Time, error) {
|
||||
return job.Schedule()
|
||||
}
|
||||
|
||||
func (s *dbScheduler) UpdateJob(ctx context.Context, job entity.DbJob) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
_ = s.runner.Update(ctx, job)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) Close() {
|
||||
s.runner.Close()
|
||||
}
|
||||
|
||||
func (s *dbScheduler) AddJob(ctx context.Context, jobs any) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
reflectValue := reflect.ValueOf(jobs)
|
||||
switch reflectValue.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
reflectLen := reflectValue.Len()
|
||||
for i := 0; i < reflectLen; i++ {
|
||||
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
||||
_ = s.runner.Add(ctx, job)
|
||||
}
|
||||
default:
|
||||
job := jobs.(entity.DbJob)
|
||||
_ = s.runner.Add(ctx, job)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) RemoveJob(ctx context.Context, jobType entity.DbJobType, jobId uint64) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if err := s.runner.Remove(ctx, entity.FormatJobKey(jobType, jobId)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) EnableJob(ctx context.Context, job entity.DbJob) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
_ = s.runner.Add(ctx, job)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) DisableJob(ctx context.Context, jobType entity.DbJobType, jobId uint64) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
_ = s.runner.Remove(ctx, entity.FormatJobKey(jobType, jobId))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) StartJobNow(ctx context.Context, job entity.DbJob) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
_ = s.runner.StartNow(ctx, job)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) backup(ctx context.Context, dbProgram dbi.DbProgram, backup *entity.DbBackup) error {
|
||||
id, err := NewIncUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
history := &entity.DbBackupHistory{
|
||||
Uuid: id.String(),
|
||||
DbBackupId: backup.Id,
|
||||
DbInstanceId: backup.DbInstanceId,
|
||||
DbName: backup.DbName,
|
||||
}
|
||||
binlogInfo, err := dbProgram.Backup(ctx, history)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
name := backup.DbName
|
||||
if len(backup.Name) > 0 {
|
||||
name = fmt.Sprintf("%s-%s", backup.DbName, backup.Name)
|
||||
}
|
||||
history.Name = fmt.Sprintf("%s[%s]", name, now.Format(time.DateTime))
|
||||
history.CreateTime = now
|
||||
history.BinlogFileName = binlogInfo.FileName
|
||||
history.BinlogSequence = binlogInfo.Sequence
|
||||
history.BinlogPosition = binlogInfo.Position
|
||||
|
||||
if err := s.backupHistoryRepo.Insert(ctx, history); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) singleFlightFetchBinlog(ctx context.Context, dbProgram dbi.DbProgram, instanceId uint64, targetTime time.Time) error {
|
||||
key := strconv.FormatUint(instanceId, 10)
|
||||
for ctx.Err() == nil {
|
||||
c := s.sfGroup.DoChan(key, func() (interface{}, error) {
|
||||
if err := s.fetchBinlog(ctx, dbProgram, instanceId, true, targetTime); err != nil {
|
||||
return targetTime, err
|
||||
}
|
||||
return targetTime, nil
|
||||
})
|
||||
select {
|
||||
case res := <-c:
|
||||
if targetTime.Compare(res.Val.(time.Time)) <= 0 {
|
||||
return res.Err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (s *dbScheduler) restore(ctx context.Context, dbProgram dbi.DbProgram, restore *entity.DbRestore) error {
|
||||
if restore.PointInTime.Valid {
|
||||
if err := s.fetchBinlog(ctx, dbProgram, restore.DbInstanceId, true, restore.PointInTime.Time); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.restorePointInTime(ctx, dbProgram, restore); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
backupHistory, err := s.backupHistoryRepo.GetById(restore.DbBackupHistoryId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = errors.New("备份历史已删除")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := s.restoreBackupHistory(ctx, dbProgram, backupHistory); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
history := &entity.DbRestoreHistory{
|
||||
CreateTime: time.Now(),
|
||||
DbRestoreId: restore.Id,
|
||||
}
|
||||
if err := s.restoreHistoryRepo.Insert(ctx, history); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) updateJob(ctx context.Context, job entity.DbJob) error {
|
||||
switch t := job.(type) {
|
||||
case *entity.DbBackup:
|
||||
return s.backupRepo.UpdateById(ctx, t)
|
||||
case *entity.DbRestore:
|
||||
return s.restoreRepo.UpdateById(ctx, t)
|
||||
case *entity.DbBinlog:
|
||||
return s.binlogRepo.UpdateById(ctx, t)
|
||||
default:
|
||||
return fmt.Errorf("无效的数据库任务类型: %T", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *dbScheduler) runJob(ctx context.Context, job entity.DbJob) error {
|
||||
conn, err := s.dbApp.GetDbConnByInstanceId(job.GetInstanceId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbProgram, err := conn.GetDialect().GetDbProgram()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch t := job.(type) {
|
||||
case *entity.DbBackup:
|
||||
return s.backup(ctx, dbProgram, t)
|
||||
case *entity.DbRestore:
|
||||
return s.restore(ctx, dbProgram, t)
|
||||
case *entity.DbBinlog:
|
||||
return s.fetchBinlog(ctx, dbProgram, t.DbInstanceId, false, time.Now())
|
||||
default:
|
||||
return fmt.Errorf("无效的数据库任务类型: %T", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *dbScheduler) runnableJob(job entity.DbJob, nextRunning runner.NextJobFunc[entity.DbJob]) (bool, error) {
|
||||
if job.IsExpired() {
|
||||
return false, runner.ErrJobExpired
|
||||
}
|
||||
const maxCountByInstanceId = 4
|
||||
const maxCountByDbName = 1
|
||||
var countByInstanceId, countByDbName int
|
||||
for item, ok := nextRunning(); ok; item, ok = nextRunning() {
|
||||
if job.GetInstanceId() == item.GetInstanceId() {
|
||||
countByInstanceId++
|
||||
if countByInstanceId >= maxCountByInstanceId {
|
||||
return false, nil
|
||||
}
|
||||
if job.GetDbName() == item.GetDbName() {
|
||||
countByDbName++
|
||||
if countByDbName >= maxCountByDbName {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if (job.GetJobType() == entity.DbJobTypeBinlog && item.GetJobType() == entity.DbJobTypeRestore) ||
|
||||
(job.GetJobType() == entity.DbJobTypeRestore && item.GetJobType() == entity.DbJobTypeBinlog) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) restorePointInTime(ctx context.Context, dbProgram dbi.DbProgram, job *entity.DbRestore) error {
|
||||
binlogHistory, err := s.binlogHistoryRepo.GetHistoryByTime(job.DbInstanceId, job.PointInTime.Time)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
position, err := dbProgram.GetBinlogEventPositionAtOrAfterTime(ctx, binlogHistory.FileName, job.PointInTime.Time)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target := &entity.BinlogInfo{
|
||||
FileName: binlogHistory.FileName,
|
||||
Sequence: binlogHistory.Sequence,
|
||||
Position: position,
|
||||
}
|
||||
backupHistory, err := s.backupHistoryRepo.GetLatestHistoryForBinlog(job.DbInstanceId, job.DbName, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start := &entity.BinlogInfo{
|
||||
FileName: backupHistory.BinlogFileName,
|
||||
Sequence: backupHistory.BinlogSequence,
|
||||
Position: backupHistory.BinlogPosition,
|
||||
}
|
||||
binlogHistories, err := s.binlogHistoryRepo.GetHistories(job.DbInstanceId, start, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
restoreInfo := &dbi.RestoreInfo{
|
||||
BackupHistory: backupHistory,
|
||||
BinlogHistories: binlogHistories,
|
||||
StartPosition: backupHistory.BinlogPosition,
|
||||
TargetPosition: target.Position,
|
||||
TargetTime: job.PointInTime.Time,
|
||||
}
|
||||
if err := dbProgram.ReplayBinlog(ctx, job.DbName, job.DbName, restoreInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.restoreBackupHistory(ctx, dbProgram, backupHistory); err != nil {
|
||||
return err
|
||||
}
|
||||
// 由于 ReplayBinlog 未记录 BINLOG 事件,系统自动备份,避免数据丢失
|
||||
backup := &entity.DbBackup{
|
||||
DbInstanceId: backupHistory.DbInstanceId,
|
||||
DbName: backupHistory.DbName,
|
||||
Enabled: true,
|
||||
Repeated: false,
|
||||
StartTime: time.Now(),
|
||||
Interval: 0,
|
||||
Name: "系统备份",
|
||||
}
|
||||
backup.Id = backupHistory.DbBackupId
|
||||
if err := s.backup(ctx, dbProgram, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) restoreBackupHistory(ctx context.Context, program dbi.DbProgram, backupHistory *entity.DbBackupHistory) (retErr error) {
|
||||
if _, err := s.backupHistoryRepo.UpdateRestoring(false, backupHistory.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := s.backupHistoryRepo.UpdateRestoring(true, backupHistory.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_, err = s.backupHistoryRepo.UpdateRestoring(false, backupHistory.Id)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
return
|
||||
}
|
||||
retErr = fmt.Errorf("%w, %w", retErr, err)
|
||||
}()
|
||||
if !ok {
|
||||
return errors.New("关联的数据库备份历史已删除")
|
||||
}
|
||||
return program.RestoreBackupHistory(ctx, backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid)
|
||||
}
|
||||
|
||||
func (s *dbScheduler) fetchBinlog(ctx context.Context, dbProgram dbi.DbProgram, instanceId uint64, downloadLatestBinlogFile bool, targetTime time.Time) error {
|
||||
if enabled, err := dbProgram.CheckBinlogEnabled(ctx); err != nil {
|
||||
return err
|
||||
} else if !enabled {
|
||||
return errors.New("数据库未启用 BINLOG")
|
||||
}
|
||||
if enabled, err := dbProgram.CheckBinlogRowFormat(ctx); err != nil {
|
||||
return err
|
||||
} else if !enabled {
|
||||
return errors.New("数据库未启用 BINLOG 行模式")
|
||||
}
|
||||
|
||||
earliestBackupSequence := int64(-1)
|
||||
binlogHistory, ok, err := s.binlogHistoryRepo.GetLatestHistory(instanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if downloadLatestBinlogFile && targetTime.Before(binlogHistory.LastEventTime) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ok {
|
||||
backupHistory, ok, err := s.backupHistoryRepo.GetEarliestHistoryForBinlog(instanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
earliestBackupSequence = backupHistory.BinlogSequence
|
||||
}
|
||||
|
||||
// todo: 将循环从 dbProgram.FetchBinlogs 中提取出来,实现 BINLOG 同步成功后逐一保存 binlogHistory
|
||||
binlogFiles, err := dbProgram.FetchBinlogs(ctx, downloadLatestBinlogFile, earliestBackupSequence, binlogHistory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.binlogHistoryRepo.InsertWithBinlogFiles(ctx, instanceId, binlogFiles)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ type DbSqlExec interface {
|
||||
DeleteBy(ctx context.Context, condition *entity.DbSqlExec) error
|
||||
|
||||
// 分页获取
|
||||
GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DbSqlExecQuery, orderBy ...string) (*model.PageResult[*entity.DbSqlExec], error)
|
||||
}
|
||||
|
||||
var _ (DbSqlExec) = (*dbSqlExecAppImpl)(nil)
|
||||
@@ -313,8 +313,8 @@ func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSql
|
||||
return d.dbSqlExecRepo.DeleteByCond(ctx, condition)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, orderBy ...string) (*model.PageResult[*entity.DbSqlExec], error) {
|
||||
return d.dbSqlExecRepo.GetPageList(condition, orderBy...)
|
||||
}
|
||||
|
||||
// 保存sql执行记录,如果是查询类则根据系统配置判断是否保存
|
||||
|
||||
@@ -34,7 +34,7 @@ type DbTransferTask interface {
|
||||
base.App[*entity.DbTransferTask]
|
||||
|
||||
// GetPageList 分页获取数据库实例
|
||||
GetPageList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DbTransferTaskQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferTask], error)
|
||||
|
||||
Save(ctx context.Context, instanceEntity *entity.DbTransferTask) error
|
||||
|
||||
@@ -69,8 +69,8 @@ type dbTransferAppImpl struct {
|
||||
fileApp fileapp.File `inject:"T"`
|
||||
}
|
||||
|
||||
func (app *dbTransferAppImpl) GetPageList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.GetRepo().GetTaskList(condition, pageParam, toEntity, orderBy...)
|
||||
func (app *dbTransferAppImpl) GetPageList(condition *entity.DbTransferTaskQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferTask], error) {
|
||||
return app.GetRepo().GetTaskList(condition, orderBy...)
|
||||
}
|
||||
|
||||
func (app *dbTransferAppImpl) Save(ctx context.Context, taskEntity *entity.DbTransferTask) error {
|
||||
@@ -144,16 +144,15 @@ func (app *dbTransferAppImpl) InitCronJob() {
|
||||
_ = app.transferFileApp.UpdateByCond(context.TODO(), &entity.DbTransferFile{Status: entity.DbTransferFileStatusFail}, &entity.DbTransferFile{Status: entity.DbTransferFileStatusRunning})
|
||||
|
||||
// 把所有需要定时执行的任务添加到定时任务中
|
||||
pageParam := &model.PageParam{
|
||||
PageSize: 100,
|
||||
PageNum: 1,
|
||||
}
|
||||
cond := new(entity.DbTransferTaskQuery)
|
||||
cond.PageNum = 1
|
||||
cond.PageSize = 100
|
||||
|
||||
cond.Status = entity.DbTransferTaskStatusEnable
|
||||
cond.CronAble = entity.DbTransferTaskCronAbleEnable
|
||||
jobs := new([]entity.DbTransferTask)
|
||||
jobs := []entity.DbTransferTask{}
|
||||
|
||||
pr, _ := app.GetPageList(cond, pageParam, jobs)
|
||||
pr, _ := app.GetPageList(cond)
|
||||
if nil == pr || pr.Total == 0 {
|
||||
return
|
||||
}
|
||||
@@ -161,15 +160,15 @@ func (app *dbTransferAppImpl) InitCronJob() {
|
||||
add := 0
|
||||
|
||||
for {
|
||||
for _, job := range *jobs {
|
||||
for _, job := range jobs {
|
||||
app.AddCronJob(contextx.NewTraceId(), &job)
|
||||
add++
|
||||
}
|
||||
if add >= int(total) {
|
||||
return
|
||||
}
|
||||
pageParam.PageNum++
|
||||
_, _ = app.GetPageList(cond, pageParam, jobs)
|
||||
cond.PageNum++
|
||||
_, _ = app.GetPageList(cond)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ type DbTransferFile interface {
|
||||
base.App[*entity.DbTransferFile]
|
||||
|
||||
// GetPageList 分页获取数据库实例
|
||||
GetPageList(condition *entity.DbTransferFileQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DbTransferFileQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferFile], error)
|
||||
|
||||
Save(ctx context.Context, instanceEntity *entity.DbTransferFile) error
|
||||
|
||||
@@ -28,8 +28,8 @@ type dbTransferFileAppImpl struct {
|
||||
fileApp fileapp.File `inject:"T"`
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) GetPageList(condition *entity.DbTransferFileQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return app.GetRepo().GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
func (app *dbTransferFileAppImpl) GetPageList(condition *entity.DbTransferFileQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferFile], error) {
|
||||
return app.GetRepo().GetPageList(condition, orderBy...)
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) Save(ctx context.Context, taskEntity *entity.DbTransferFile) error {
|
||||
|
||||
@@ -2,8 +2,6 @@ package dbi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -11,9 +9,9 @@ type DbProgram interface {
|
||||
CheckBinlogEnabled(ctx context.Context) (bool, error)
|
||||
CheckBinlogRowFormat(ctx context.Context) (bool, error)
|
||||
|
||||
Backup(ctx context.Context, backupHistory *entity.DbBackupHistory) (*entity.BinlogInfo, error)
|
||||
// Backup(ctx context.Context, backupHistory *entity.DbBackupHistory) (*entity.BinlogInfo, error)
|
||||
|
||||
FetchBinlogs(ctx context.Context, downloadLatestBinlogFile bool, earliestBackupSequence int64, latestBinlogHistory *entity.DbBinlogHistory) ([]*entity.BinlogFile, error)
|
||||
// FetchBinlogs(ctx context.Context, downloadLatestBinlogFile bool, earliestBackupSequence int64, latestBinlogHistory *entity.DbBinlogHistory) ([]*entity.BinlogFile, error)
|
||||
|
||||
ReplayBinlog(ctx context.Context, originalDatabase, targetDatabase string, restoreInfo *RestoreInfo) error
|
||||
|
||||
@@ -23,21 +21,22 @@ type DbProgram interface {
|
||||
|
||||
GetBinlogEventPositionAtOrAfterTime(ctx context.Context, binlogName string, targetTime time.Time) (position int64, parseErr error)
|
||||
|
||||
PruneBinlog(history *entity.DbBinlogHistory) error
|
||||
// PruneBinlog(history *entity.DbBinlogHistory) error
|
||||
}
|
||||
|
||||
type RestoreInfo struct {
|
||||
BackupHistory *entity.DbBackupHistory
|
||||
BinlogHistories []*entity.DbBinlogHistory
|
||||
StartPosition int64
|
||||
TargetPosition int64
|
||||
TargetTime time.Time
|
||||
// BackupHistory *entity.DbBackupHistory
|
||||
// BinlogHistories []*entity.DbBinlogHistory
|
||||
StartPosition int64
|
||||
TargetPosition int64
|
||||
TargetTime time.Time
|
||||
}
|
||||
|
||||
func (ri *RestoreInfo) GetBinlogPaths(binlogDir string) []string {
|
||||
files := make([]string, 0, len(ri.BinlogHistories))
|
||||
for _, history := range ri.BinlogHistories {
|
||||
files = append(files, filepath.Join(binlogDir, history.FileName))
|
||||
}
|
||||
return files
|
||||
// files := make([]string, 0, len(ri.BinlogHistories))
|
||||
// for _, history := range ri.BinlogHistories {
|
||||
// files = append(files, filepath.Join(binlogDir, history.FileName))
|
||||
// }
|
||||
// return files
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ type MysqlDialect struct {
|
||||
|
||||
// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
|
||||
func (md *MysqlDialect) GetDbProgram() (dbi.DbProgram, error) {
|
||||
return NewDbProgramMysql(md.dc), nil
|
||||
return nil, nil
|
||||
// return NewDbProgramMysql(md.dc), nil
|
||||
}
|
||||
|
||||
func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,18 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"strings"
|
||||
"testing"
|
||||
// func Test_readBinlogInfoFromBackup(t *testing.T) {
|
||||
// text := `
|
||||
// --
|
||||
// -- Position to start replication or point-in-time recovery from
|
||||
// --
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_readBinlogInfoFromBackup(t *testing.T) {
|
||||
text := `
|
||||
--
|
||||
-- Position to start replication or point-in-time recovery from
|
||||
--
|
||||
|
||||
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000003', MASTER_LOG_POS=379;
|
||||
`
|
||||
got, err := readBinlogInfoFromBackup(strings.NewReader(text))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &entity.BinlogInfo{
|
||||
FileName: "binlog.000003",
|
||||
Sequence: 3,
|
||||
Position: 379,
|
||||
}, got)
|
||||
}
|
||||
// -- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000003', MASTER_LOG_POS=379;
|
||||
// `
|
||||
// got, err := readBinlogInfoFromBackup(strings.NewReader(text))
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, &entity.BinlogInfo{
|
||||
// FileName: "binlog.000003",
|
||||
// Sequence: 3,
|
||||
// Position: 379,
|
||||
// }, got)
|
||||
// }
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/runner"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ DbJob = (*DbBackup)(nil)
|
||||
|
||||
// DbBackup 数据库备份任务
|
||||
type DbBackup struct {
|
||||
DbJobBaseImpl
|
||||
|
||||
DbInstanceId uint64 // 数据库实例ID
|
||||
DbName string // 数据库名称
|
||||
Name string // 数据库备份名称
|
||||
Enabled bool // 是否启用
|
||||
EnabledDesc string // 启用状态描述
|
||||
StartTime time.Time // 开始时间
|
||||
Interval time.Duration // 间隔时间
|
||||
MaxSaveDays int // 数据库备份历史保留天数,过期将自动删除
|
||||
Repeated bool // 是否重复执行
|
||||
}
|
||||
|
||||
func (b *DbBackup) GetInstanceId() uint64 {
|
||||
return b.DbInstanceId
|
||||
}
|
||||
|
||||
func (b *DbBackup) GetDbName() string {
|
||||
return b.DbName
|
||||
}
|
||||
|
||||
func (b *DbBackup) GetJobType() DbJobType {
|
||||
return DbJobTypeBackup
|
||||
}
|
||||
|
||||
func (b *DbBackup) Schedule() (time.Time, error) {
|
||||
if b.IsFinished() {
|
||||
return time.Time{}, runner.ErrJobFinished
|
||||
}
|
||||
if !b.Enabled {
|
||||
return time.Time{}, runner.ErrJobDisabled
|
||||
}
|
||||
switch b.LastStatus {
|
||||
case DbJobSuccess:
|
||||
lastTime := b.LastTime.Time
|
||||
if lastTime.Before(b.StartTime) {
|
||||
lastTime = b.StartTime.Add(-b.Interval)
|
||||
}
|
||||
return lastTime.Add(b.Interval - lastTime.Sub(b.StartTime)%b.Interval), nil
|
||||
case DbJobRunning, DbJobFailed:
|
||||
return time.Now().Add(time.Minute), nil
|
||||
default:
|
||||
return b.StartTime, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *DbBackup) IsFinished() bool {
|
||||
return !b.Repeated && b.LastStatus == DbJobSuccess
|
||||
}
|
||||
|
||||
func (b *DbBackup) IsEnabled() bool {
|
||||
return b.Enabled
|
||||
}
|
||||
|
||||
func (b *DbBackup) IsExpired() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *DbBackup) SetEnabled(enabled bool, desc string) {
|
||||
b.Enabled = enabled
|
||||
b.EnabledDesc = desc
|
||||
}
|
||||
|
||||
func (b *DbBackup) Update(job runner.Job) {
|
||||
backup := job.(*DbBackup)
|
||||
b.StartTime = backup.StartTime
|
||||
b.Interval = backup.Interval
|
||||
}
|
||||
|
||||
func (b *DbBackup) GetInterval() time.Duration {
|
||||
return b.Interval
|
||||
}
|
||||
|
||||
func (b *DbBackup) GetKey() DbJobKey {
|
||||
return b.getKey(b.GetJobType())
|
||||
}
|
||||
|
||||
func (b *DbBackup) SetStatus(status runner.JobStatus, err error) {
|
||||
b.setLastStatus(b.GetJobType(), status, err)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DbBackupHistory 数据库备份历史
|
||||
type DbBackupHistory struct {
|
||||
model.DeletedModel
|
||||
|
||||
Uuid string `json:"uuid"`
|
||||
Name string `json:"name"` // 备份历史名称
|
||||
CreateTime time.Time `json:"createTime"` // 创建时间: 2023-11-08 02:00:00
|
||||
DbBackupId uint64 `json:"dbBackupId"`
|
||||
DbInstanceId uint64 `json:"dbInstanceId"`
|
||||
DbName string `json:"dbName"`
|
||||
BinlogFileName string `json:"binlogFileName"`
|
||||
BinlogSequence int64 `json:"binlogSequence"`
|
||||
BinlogPosition int64 `json:"binlogPosition"`
|
||||
}
|
||||
|
||||
func (d *DbBackupHistory) TableName() string {
|
||||
return "t_db_backup_history"
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/runner"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
BinlogDownloadInterval = time.Minute * 15
|
||||
)
|
||||
|
||||
// BinlogFile is the metadata of the MySQL binlog file.
|
||||
type BinlogFile struct {
|
||||
Name string
|
||||
RemoteSize int64
|
||||
LocalSize int64
|
||||
|
||||
// Sequence is parsed from Name and is for the sorting purpose.
|
||||
Sequence int64
|
||||
FirstEventTime time.Time
|
||||
LastEventTime time.Time
|
||||
|
||||
Downloaded bool
|
||||
}
|
||||
|
||||
var _ DbJob = (*DbBinlog)(nil)
|
||||
|
||||
// DbBinlog 数据库备份任务
|
||||
type DbBinlog struct {
|
||||
DbJobBaseImpl
|
||||
DbInstanceId uint64 // 数据库实例ID
|
||||
}
|
||||
|
||||
func NewDbBinlog(instanceId uint64) *DbBinlog {
|
||||
job := &DbBinlog{}
|
||||
job.Id = instanceId
|
||||
job.DbInstanceId = instanceId
|
||||
return job
|
||||
}
|
||||
|
||||
func (b *DbBinlog) GetInstanceId() uint64 {
|
||||
return b.DbInstanceId
|
||||
}
|
||||
|
||||
func (b *DbBinlog) GetDbName() string {
|
||||
// binlog 是全库级别的
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *DbBinlog) Schedule() (time.Time, error) {
|
||||
switch b.LastStatus {
|
||||
case DbJobSuccess:
|
||||
return time.Time{}, runner.ErrJobFinished
|
||||
case DbJobFailed:
|
||||
return time.Now().Add(BinlogDownloadInterval), nil
|
||||
default:
|
||||
return time.Now(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *DbBinlog) Update(_ runner.Job) {}
|
||||
|
||||
func (b *DbBinlog) IsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *DbBinlog) IsExpired() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *DbBinlog) SetEnabled(_ bool, _ string) {}
|
||||
|
||||
func (b *DbBinlog) GetInterval() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (b *DbBinlog) GetJobType() DbJobType {
|
||||
return DbJobTypeBinlog
|
||||
}
|
||||
|
||||
func (b *DbBinlog) GetKey() DbJobKey {
|
||||
return b.getKey(b.GetJobType())
|
||||
}
|
||||
|
||||
func (b *DbBinlog) SetStatus(status DbJobStatus, err error) {
|
||||
b.setLastStatus(b.GetJobType(), status, err)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DbBinlogHistory 数据库 binlog 历史
|
||||
type DbBinlogHistory struct {
|
||||
model.DeletedModel
|
||||
|
||||
CreateTime time.Time `json:"createTime"` // 创建时间: 2023-11-08 02:00:00
|
||||
FileName string
|
||||
FileSize int64
|
||||
Sequence int64
|
||||
FirstEventTime time.Time
|
||||
LastEventTime time.Time
|
||||
DbInstanceId uint64 `json:"dbInstanceId"`
|
||||
}
|
||||
|
||||
func (d *DbBinlogHistory) TableName() string {
|
||||
return "t_db_binlog_history"
|
||||
}
|
||||
|
||||
type BinlogInfo struct {
|
||||
FileName string `json:"fileName"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Position int64 `json:"position"`
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/runner"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
)
|
||||
|
||||
const LastResultSize = 256
|
||||
|
||||
type DbJobKey = runner.JobKey
|
||||
|
||||
type DbJobStatus = runner.JobStatus
|
||||
|
||||
const (
|
||||
DbJobRunning = runner.JobRunning
|
||||
DbJobSuccess = runner.JobSuccess
|
||||
DbJobFailed = runner.JobFailed
|
||||
)
|
||||
|
||||
type DbJobType string
|
||||
|
||||
func (typ DbJobType) String() string {
|
||||
return string(typ)
|
||||
}
|
||||
|
||||
const (
|
||||
DbJobUnknown DbJobType = "db-unknown"
|
||||
DbJobTypeBackup DbJobType = "db-backup"
|
||||
DbJobTypeRestore DbJobType = "db-restore"
|
||||
DbJobTypeBinlog DbJobType = "db-binlog"
|
||||
)
|
||||
|
||||
const (
|
||||
DbJobNameUnknown = "未知任务"
|
||||
DbJobNameBackup = "数据库备份"
|
||||
DbJobNameRestore = "数据库恢复"
|
||||
DbJobNameBinlog = "BINLOG同步"
|
||||
)
|
||||
|
||||
var _ runner.Job = (DbJob)(nil)
|
||||
|
||||
type DbJobBase interface {
|
||||
model.ModelI
|
||||
}
|
||||
|
||||
type DbJob interface {
|
||||
runner.Job
|
||||
DbJobBase
|
||||
|
||||
GetInstanceId() uint64
|
||||
GetKey() string
|
||||
GetJobType() DbJobType
|
||||
GetDbName() string
|
||||
Schedule() (time.Time, error)
|
||||
IsEnabled() bool
|
||||
IsExpired() bool
|
||||
SetEnabled(enabled bool, desc string)
|
||||
Update(job runner.Job)
|
||||
GetInterval() time.Duration
|
||||
}
|
||||
|
||||
var _ DbJobBase = (*DbJobBaseImpl)(nil)
|
||||
|
||||
type DbJobBaseImpl struct {
|
||||
model.Model
|
||||
|
||||
LastStatus DbJobStatus // 最近一次执行状态
|
||||
LastResult string // 最近一次执行结果
|
||||
LastTime timex.NullTime // 最近一次执行时间
|
||||
jobKey runner.JobKey
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) getJobType() DbJobType {
|
||||
job, ok := any(d).(DbJob)
|
||||
if !ok {
|
||||
return DbJobUnknown
|
||||
}
|
||||
return job.GetJobType()
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) setLastStatus(jobType DbJobType, status DbJobStatus, err error) {
|
||||
var statusName, jobName string
|
||||
switch status {
|
||||
case DbJobRunning:
|
||||
statusName = "运行中"
|
||||
case DbJobSuccess:
|
||||
statusName = "成功"
|
||||
case DbJobFailed:
|
||||
statusName = "失败"
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
switch jobType {
|
||||
case DbJobTypeBackup:
|
||||
jobName = DbJobNameBackup
|
||||
case DbJobTypeRestore:
|
||||
jobName = DbJobNameRestore
|
||||
case DbJobTypeBinlog:
|
||||
jobName = DbJobNameBinlog
|
||||
default:
|
||||
jobName = jobType.String()
|
||||
}
|
||||
d.LastStatus = status
|
||||
var result = jobName + statusName
|
||||
if err != nil {
|
||||
result = fmt.Sprintf("%s: %v", result, err)
|
||||
}
|
||||
d.LastResult = stringx.Truncate(result, LastResultSize, LastResultSize, "")
|
||||
d.LastTime = timex.NewNullTime(time.Now())
|
||||
}
|
||||
|
||||
func FormatJobKey(typ DbJobType, jobId uint64) DbJobKey {
|
||||
return fmt.Sprintf("%v-%d", typ, jobId)
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) getKey(jobType DbJobType) DbJobKey {
|
||||
if len(d.jobKey) == 0 {
|
||||
d.jobKey = FormatJobKey(jobType, d.Id)
|
||||
}
|
||||
return d.jobKey
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/runner"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ DbJob = (*DbRestore)(nil)
|
||||
|
||||
// DbRestore 数据库恢复任务
|
||||
type DbRestore struct {
|
||||
DbJobBaseImpl
|
||||
|
||||
DbInstanceId uint64 // 数据库实例ID
|
||||
DbName string // 数据库名称
|
||||
Enabled bool // 是否启用
|
||||
EnabledDesc string // 启用状态描述
|
||||
StartTime time.Time // 开始时间
|
||||
Interval time.Duration // 间隔时间
|
||||
Repeated bool // 是否重复执行
|
||||
PointInTime timex.NullTime `json:"pointInTime"` // 指定数据库恢复的时间点
|
||||
DbBackupId uint64 `json:"dbBackupId"` // 用于恢复的数据库恢复任务ID
|
||||
DbBackupHistoryId uint64 `json:"dbBackupHistoryId"` // 用于恢复的数据库恢复历史ID
|
||||
DbBackupHistoryName string `json:"dbBackupHistoryName"` // 数据库恢复历史名称
|
||||
}
|
||||
|
||||
func (r *DbRestore) GetInstanceId() uint64 {
|
||||
return r.DbInstanceId
|
||||
}
|
||||
|
||||
func (r *DbRestore) GetDbName() string {
|
||||
return r.DbName
|
||||
}
|
||||
|
||||
func (r *DbRestore) Schedule() (time.Time, error) {
|
||||
if !r.Enabled {
|
||||
return time.Time{}, runner.ErrJobDisabled
|
||||
}
|
||||
switch r.LastStatus {
|
||||
case DbJobSuccess, DbJobFailed:
|
||||
return time.Time{}, runner.ErrJobFinished
|
||||
default:
|
||||
if time.Now().Sub(r.StartTime) > time.Hour {
|
||||
return time.Time{}, runner.ErrJobExpired
|
||||
}
|
||||
return r.StartTime, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DbRestore) IsEnabled() bool {
|
||||
return r.Enabled
|
||||
}
|
||||
|
||||
func (r *DbRestore) SetEnabled(enabled bool, desc string) {
|
||||
r.Enabled = enabled
|
||||
r.EnabledDesc = desc
|
||||
}
|
||||
|
||||
func (r *DbRestore) IsExpired() bool {
|
||||
return !r.Repeated && time.Now().After(r.StartTime.Add(time.Hour))
|
||||
}
|
||||
|
||||
func (r *DbRestore) IsFinished() bool {
|
||||
return !r.Repeated && r.LastStatus == DbJobSuccess
|
||||
}
|
||||
|
||||
func (r *DbRestore) Update(job runner.Job) {
|
||||
restore := job.(*DbRestore)
|
||||
r.StartTime = restore.StartTime
|
||||
r.Interval = restore.Interval
|
||||
}
|
||||
|
||||
func (r *DbRestore) GetInterval() time.Duration {
|
||||
return r.Interval
|
||||
}
|
||||
|
||||
func (r *DbRestore) GetJobType() DbJobType {
|
||||
return DbJobTypeRestore
|
||||
}
|
||||
|
||||
func (r *DbRestore) GetKey() DbJobKey {
|
||||
return r.getKey(r.GetJobType())
|
||||
}
|
||||
|
||||
func (r *DbRestore) SetStatus(status DbJobStatus, err error) {
|
||||
r.setLastStatus(r.GetJobType(), status, err)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DbRestoreHistory 数据库恢复历史
|
||||
type DbRestoreHistory struct {
|
||||
model.DeletedModel
|
||||
|
||||
CreateTime time.Time `orm:"column(create_time)" json:"createTime"` // 创建时间: 2023-11-08 02:00:00
|
||||
DbRestoreId uint64 `orm:"column(db_restore_id)" json:"dbRestoreId"`
|
||||
}
|
||||
|
||||
func (d *DbRestoreHistory) TableName() string {
|
||||
return "t_db_restore_history"
|
||||
}
|
||||
20
server/internal/db/domain/entity/po.go
Normal file
20
server/internal/db/domain/entity/po.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type DbListPO struct {
|
||||
Id *int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name *string `json:"name"`
|
||||
GetDatabaseMode DbGetDatabaseMode `json:"getDatabaseMode"` // 获取数据库方式
|
||||
Database *string `json:"database"`
|
||||
Remark *string `json:"remark"`
|
||||
InstanceId uint64 `json:"instanceId"`
|
||||
AuthCertName string `json:"authCertName"`
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Creator *string `json:"creator"`
|
||||
CreatorId *int64 `json:"creatorId"`
|
||||
UpdateTime *time.Time `json:"updateTime"`
|
||||
Modifier *string `json:"modifier"`
|
||||
ModifierId *int64 `json:"modifierId"`
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
// InstanceQuery 数据库实例查询
|
||||
type InstanceQuery struct {
|
||||
model.PageParam
|
||||
|
||||
Id uint64 `json:"id" form:"id"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Code string `json:"code" form:"code"`
|
||||
@@ -12,19 +16,27 @@ type InstanceQuery struct {
|
||||
}
|
||||
|
||||
type DataSyncTaskQuery struct {
|
||||
model.PageParam
|
||||
|
||||
Name string `json:"name" form:"name"`
|
||||
Status int8 `json:"status" form:"status"`
|
||||
}
|
||||
type DataSyncLogQuery struct {
|
||||
model.PageParam
|
||||
|
||||
TaskId uint64 `json:"task_id" form:"taskId"`
|
||||
}
|
||||
|
||||
type DbTransferTaskQuery struct {
|
||||
model.PageParam
|
||||
|
||||
Name string `json:"name" form:"name"`
|
||||
Status int8 `json:"status" form:"status"`
|
||||
CronAble int8 `json:"cronAble" form:"cronAble"`
|
||||
}
|
||||
type DbTransferFileQuery struct {
|
||||
model.PageParam
|
||||
|
||||
TaskId uint64 `json:"task_id" form:"taskId"`
|
||||
Name string `json:"name" form:"name"`
|
||||
}
|
||||
@@ -35,6 +47,8 @@ type DbTransferLogQuery struct {
|
||||
|
||||
// 数据库查询实体,不与数据库表字段一一对应
|
||||
type DbQuery struct {
|
||||
model.PageParam
|
||||
|
||||
Id uint64 `form:"id"`
|
||||
TagPath string `form:"tagPath"`
|
||||
Code string `json:"code" form:"code"`
|
||||
@@ -43,6 +57,8 @@ type DbQuery struct {
|
||||
}
|
||||
|
||||
type DbSqlExecQuery struct {
|
||||
model.PageParam
|
||||
|
||||
Id uint64 `json:"id" form:"id"`
|
||||
DbId uint64 `json:"dbId" form:"dbId"`
|
||||
Db string `json:"db" form:"db"`
|
||||
@@ -76,6 +92,8 @@ type DbBackupHistoryQuery struct {
|
||||
|
||||
// DbRestoreQuery 数据库备份任务查询
|
||||
type DbRestoreQuery struct {
|
||||
*model.PageParam
|
||||
|
||||
Id uint64 `json:"id" form:"id"`
|
||||
DbName string `json:"dbName" form:"dbName"`
|
||||
InDbNames []string `json:"-" form:"-"`
|
||||
|
||||
@@ -10,5 +10,5 @@ type Db interface {
|
||||
base.Repo[*entity.Db]
|
||||
|
||||
// 分页获取数据信息列表
|
||||
GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetDbList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error)
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbBackup interface {
|
||||
DbJob[*entity.DbBackup]
|
||||
|
||||
ListToDo(jobs any) error
|
||||
ListDbInstances(enabled bool, repeated bool, instanceIds *[]uint64) error
|
||||
GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error)
|
||||
|
||||
// GetPageList 分页获取数据库任务列表
|
||||
GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbBackupHistory interface {
|
||||
base.Repo[*entity.DbBackupHistory]
|
||||
|
||||
// GetPageList 分页获取数据备份历史
|
||||
GetPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
GetLatestHistoryForBinlog(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error)
|
||||
|
||||
GetEarliestHistoryForBinlog(instanceId uint64) (*entity.DbBackupHistory, bool, error)
|
||||
|
||||
GetHistories(backupHistoryIds []uint64, toEntity any) error
|
||||
|
||||
UpdateDeleting(deleting bool, backupHistoryId ...uint64) (bool, error)
|
||||
UpdateRestoring(restoring bool, backupHistoryId ...uint64) (bool, error)
|
||||
ZeroBinlogInfo(backupHistoryId uint64) error
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
)
|
||||
|
||||
type DbBinlog interface {
|
||||
DbJob[*entity.DbBinlog]
|
||||
|
||||
AddJobIfNotExists(ctx context.Context, job *entity.DbBinlog) error
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"time"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
GetHistoriesBeforeSequence(ctx context.Context, instanceId uint64, binlogSeq int64, histories *[]*entity.DbBinlogHistory) error
|
||||
}
|
||||
@@ -10,12 +10,12 @@ type DataSyncTask interface {
|
||||
base.Repo[*entity.DataSyncTask]
|
||||
|
||||
// 分页获取数据库实例信息列表
|
||||
GetTaskList(condition *entity.DataSyncTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetTaskList(condition *entity.DataSyncTaskQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncTask], error)
|
||||
}
|
||||
|
||||
type DataSyncLog interface {
|
||||
base.Repo[*entity.DataSyncLog]
|
||||
|
||||
// 分页获取数据库实例信息列表
|
||||
GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetTaskLogList(condition *entity.DataSyncLogQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncLog], error)
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type DbJobBase[T entity.DbJob] interface {
|
||||
base.Repo[T]
|
||||
|
||||
// UpdateLastStatus 更新任务执行状态
|
||||
UpdateLastStatus(ctx context.Context, job entity.DbJob) error
|
||||
}
|
||||
|
||||
type DbJob[T entity.DbJob] interface {
|
||||
DbJobBase[T]
|
||||
|
||||
// AddJob 添加数据库任务
|
||||
AddJob(ctx context.Context, jobs any) error
|
||||
UpdateEnabled(ctx context.Context, jobId uint64, enabled bool) error
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbRestore interface {
|
||||
DbJob[*entity.DbRestore]
|
||||
|
||||
ListToDo(jobs any) error
|
||||
GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error)
|
||||
|
||||
// GetPageList 分页获取数据库任务列表
|
||||
GetPageList(condition *entity.DbRestoreQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
GetEnabledRestores(toEntity any, backupHistoryId ...uint64) error
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbRestoreHistory interface {
|
||||
base.Repo[*entity.DbRestoreHistory]
|
||||
|
||||
// GetDbRestoreHistories 分页获取数据备份历史
|
||||
GetDbRestoreHistories(condition *entity.DbRestoreHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
@@ -10,5 +10,5 @@ type DbSqlExec interface {
|
||||
base.Repo[*entity.DbSqlExec]
|
||||
|
||||
// 分页获取
|
||||
GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DbSqlExecQuery, orderBy ...string) (*model.PageResult[*entity.DbSqlExec], error)
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ type DbTransferTask interface {
|
||||
base.Repo[*entity.DbTransferTask]
|
||||
|
||||
// 分页获取数据库实例信息列表
|
||||
GetTaskList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetTaskList(condition *entity.DbTransferTaskQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferTask], error)
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ type DbTransferFile interface {
|
||||
base.Repo[*entity.DbTransferFile]
|
||||
|
||||
// 分页获取数据库实例信息列表
|
||||
GetPageList(condition *entity.DbTransferFileQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.DbTransferFileQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferFile], error)
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ type Instance interface {
|
||||
base.Repo[*entity.DbInstance]
|
||||
|
||||
// 分页获取数据库实例信息列表
|
||||
GetInstanceList(condition *entity.InstanceQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetInstanceList(condition *entity.InstanceQuery, orderBy ...string) (*model.PageResult[*entity.DbInstance], error)
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package repository
|
||||
|
||||
type Repositories struct {
|
||||
Instance Instance
|
||||
Backup DbBackup
|
||||
BackupHistory DbBackupHistory
|
||||
Restore DbRestore
|
||||
RestoreHistory DbRestoreHistory
|
||||
Binlog DbBinlog
|
||||
BinlogHistory DbBinlogHistory
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
@@ -16,7 +17,8 @@ func newDbRepo() repository.Db {
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error) {
|
||||
pd := model.NewCond().Eq("instance_id", condition.InstanceId).In("code", condition.Codes).Eq("id", condition.Id)
|
||||
return d.PageByCondToAny(pd, pageParam, toEntity)
|
||||
list := []*entity.DbListPO{}
|
||||
return gormx.PageByCond(d.GetModel(), pd, condition.PageParam, list)
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
"slices"
|
||||
)
|
||||
|
||||
var _ repository.DbBackup = (*dbBackupRepoImpl)(nil)
|
||||
|
||||
type dbBackupRepoImpl struct {
|
||||
dbJobBaseImpl[*entity.DbBackup]
|
||||
}
|
||||
|
||||
func NewDbBackupRepo() repository.DbBackup {
|
||||
dr := &dbBackupRepoImpl{}
|
||||
return dr
|
||||
}
|
||||
|
||||
func (d *dbBackupRepoImpl) GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error) {
|
||||
var dbNamesWithBackup []string
|
||||
err := global.Db.Model(d.NewModel()).
|
||||
Where("db_instance_id = ?", instanceId).
|
||||
Where("repeated = ?", true).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Pluck("db_name", &dbNamesWithBackup).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]string, 0, len(dbNames))
|
||||
for _, name := range dbNames {
|
||||
if !slices.Contains(dbNamesWithBackup, name) {
|
||||
result = append(result, name)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *dbBackupRepoImpl) ListDbInstances(enabled bool, repeated bool, instanceIds *[]uint64) error {
|
||||
return global.Db.Model(d.NewModel()).
|
||||
Where("enabled = ?", enabled).
|
||||
Where("repeated = ?", repeated).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Distinct().
|
||||
Pluck("db_instance_id", &instanceIds).
|
||||
Error
|
||||
}
|
||||
|
||||
func (d *dbBackupRepoImpl) ListToDo(jobs any) error {
|
||||
db := global.Db.Model(d.NewModel())
|
||||
err := db.Where("enabled = ?", true).
|
||||
Where(db.Where("repeated = ?", true).Or("last_status <> ?", entity.DbJobSuccess)).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Find(jobs).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageList 分页获取数据库备份任务列表
|
||||
func (d *dbBackupRepoImpl) GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("id", condition.Id).
|
||||
Eq0("db_instance_id", condition.DbInstanceId).
|
||||
Eq0("repeated", condition.Repeated).
|
||||
In0("db_name", condition.InDbNames).
|
||||
Like("db_name", condition.DbName)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
// AddJob 添加数据库任务
|
||||
func (d *dbBackupRepoImpl) AddJob(ctx context.Context, jobs any) error {
|
||||
return addJob[*entity.DbBackup](ctx, d.dbJobBaseImpl, jobs)
|
||||
}
|
||||
|
||||
func (d *dbBackupRepoImpl) UpdateEnabled(ctx context.Context, jobId uint64, enabled bool) error {
|
||||
cond := map[string]any{
|
||||
"id": jobId,
|
||||
}
|
||||
desc := "已禁用"
|
||||
if enabled {
|
||||
desc = "已启用"
|
||||
}
|
||||
return d.UpdateByCond(ctx, map[string]any{
|
||||
"enabled": enabled,
|
||||
"enabled_desc": desc,
|
||||
}, cond)
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ repository.DbBackupHistory = (*dbBackupHistoryRepoImpl)(nil)
|
||||
|
||||
type dbBackupHistoryRepoImpl struct {
|
||||
base.RepoImpl[*entity.DbBackupHistory]
|
||||
}
|
||||
|
||||
func NewDbBackupHistoryRepo() repository.DbBackupHistory {
|
||||
return &dbBackupHistoryRepoImpl{}
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) GetPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("id", condition.Id).
|
||||
Eq0("db_instance_id", condition.DbInstanceId).
|
||||
In0("db_name", condition.InDbNames).
|
||||
Eq("db_backup_id", condition.DbBackupId).
|
||||
Eq("db_name", condition.DbName)
|
||||
return repo.PageByCondToAny(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) GetHistories(backupHistoryIds []uint64, toEntity any) error {
|
||||
return global.Db.Model(repo.NewModel()).
|
||||
Where("id in ?", backupHistoryIds).
|
||||
Where("deleting = false").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Find(toEntity).
|
||||
Error
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) GetLatestHistoryForBinlog(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error) {
|
||||
history := &entity.DbBackupHistory{}
|
||||
db := global.Db
|
||||
err := db.Model(repo.NewModel()).
|
||||
Where("db_instance_id = ?", instanceId).
|
||||
Where("db_name = ?", dbName).
|
||||
Where(db.Where("binlog_sequence < ?", bi.Sequence).
|
||||
Or(db.Where("binlog_sequence = ?", bi.Sequence).
|
||||
Where("binlog_position <= ?", bi.Position))).
|
||||
Where("binlog_sequence > 0").
|
||||
Where("deleting = false").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Order("binlog_sequence desc, binlog_position desc").
|
||||
First(history).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return history, err
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) GetEarliestHistoryForBinlog(instanceId uint64) (*entity.DbBackupHistory, bool, error) {
|
||||
history := &entity.DbBackupHistory{}
|
||||
db := global.Db.Model(repo.NewModel())
|
||||
err := db.Where("db_instance_id = ?", instanceId).
|
||||
Where("binlog_sequence > 0").
|
||||
Where("deleting = false").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Order("binlog_sequence").
|
||||
First(history).Error
|
||||
switch {
|
||||
case err == nil:
|
||||
return history, true, nil
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
return history, false, nil
|
||||
default:
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) UpdateDeleting(deleting bool, backupHistoryId ...uint64) (bool, error) {
|
||||
db := global.Db.Model(repo.NewModel()).
|
||||
Where("id in ?", backupHistoryId).
|
||||
Where("restoring = false").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Update("deleting", deleting)
|
||||
if db.Error != nil {
|
||||
return false, db.Error
|
||||
}
|
||||
if db.RowsAffected != int64(len(backupHistoryId)) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) UpdateRestoring(restoring bool, backupHistoryId ...uint64) (bool, error) {
|
||||
db := global.Db.Model(repo.NewModel()).
|
||||
Where("id in ?", backupHistoryId).
|
||||
Where("deleting = false").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Update("restoring", restoring)
|
||||
if db.Error != nil {
|
||||
return false, db.Error
|
||||
}
|
||||
if db.RowsAffected != int64(len(backupHistoryId)) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (repo *dbBackupHistoryRepoImpl) ZeroBinlogInfo(backupHistoryId uint64) error {
|
||||
return global.Db.Model(repo.NewModel()).
|
||||
Where("id = ?", backupHistoryId).
|
||||
Where("restoring = false").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Updates(&map[string]any{
|
||||
"binlog_file_name": "",
|
||||
"binlog_sequence": 0,
|
||||
"binlog_position": 0,
|
||||
}).Error
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/global"
|
||||
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
var _ repository.DbBinlog = (*dbBinlogRepoImpl)(nil)
|
||||
|
||||
type dbBinlogRepoImpl struct {
|
||||
dbJobBaseImpl[*entity.DbBinlog]
|
||||
}
|
||||
|
||||
func NewDbBinlogRepo() repository.DbBinlog {
|
||||
return &dbBinlogRepoImpl{}
|
||||
}
|
||||
|
||||
func (d *dbBinlogRepoImpl) AddJobIfNotExists(_ context.Context, job *entity.DbBinlog) error {
|
||||
// todo: 如果存在已删除记录,如何处理?
|
||||
if err := global.Db.Clauses(clause.OnConflict{DoNothing: true}).Create(job).Error; err != nil {
|
||||
return fmt.Errorf("启动 binlog 下载失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddJob 添加数据库任务
|
||||
func (d *dbBinlogRepoImpl) AddJob(ctx context.Context, jobs any) error {
|
||||
panic("not implement, use AddJobIfNotExists")
|
||||
}
|
||||
|
||||
func (d *dbBinlogRepoImpl) UpdateEnabled(_ context.Context, jobId uint64, enabled bool) error {
|
||||
panic("not implement")
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ repository.DbBinlogHistory = (*dbBinlogHistoryRepoImpl)(nil)
|
||||
|
||||
type dbBinlogHistoryRepoImpl struct {
|
||||
base.RepoImpl[*entity.DbBinlogHistory]
|
||||
}
|
||||
|
||||
func NewDbBinlogHistoryRepo() repository.DbBinlogHistory {
|
||||
return &dbBinlogHistoryRepoImpl{}
|
||||
}
|
||||
|
||||
func (repo *dbBinlogHistoryRepoImpl) GetHistoryByTime(instanceId uint64, targetTime time.Time) (*entity.DbBinlogHistory, error) {
|
||||
qc := model.NewCond().
|
||||
Eq("db_instance_id", instanceId).
|
||||
Le("first_event_time", targetTime).
|
||||
OrderByDesc("first_event_time")
|
||||
history := &entity.DbBinlogHistory{}
|
||||
if err := repo.GetByCond(qc.Dest(history)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return history, nil
|
||||
}
|
||||
|
||||
func (repo *dbBinlogHistoryRepoImpl) GetHistories(instanceId uint64, start, target *entity.BinlogInfo) ([]*entity.DbBinlogHistory, error) {
|
||||
qc := model.NewCond().
|
||||
Eq("db_instance_id", instanceId).
|
||||
Ge("sequence", start.Sequence).
|
||||
Le("sequence", target.Sequence).
|
||||
OrderByAsc("sequence")
|
||||
histories, err := repo.SelectByCond(qc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(histories) == 0 {
|
||||
return nil, errors.New("未找到满足条件的 binlog 文件")
|
||||
}
|
||||
return histories, nil
|
||||
}
|
||||
|
||||
func (repo *dbBinlogHistoryRepoImpl) GetLatestHistory(instanceId uint64) (*entity.DbBinlogHistory, bool, error) {
|
||||
history := &entity.DbBinlogHistory{}
|
||||
qc := model.NewCond().
|
||||
Eq("db_instance_id", instanceId).
|
||||
OrderByDesc("sequence").
|
||||
Dest(history)
|
||||
err := repo.GetByCond(qc)
|
||||
switch {
|
||||
case err == nil:
|
||||
return history, true, nil
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
return history, false, nil
|
||||
default:
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *dbBinlogHistoryRepoImpl) Upsert(_ context.Context, history *entity.DbBinlogHistory) error {
|
||||
return gormx.Tx(func(db *gorm.DB) error {
|
||||
old := &entity.DbBinlogHistory{}
|
||||
err := db.Where("db_instance_id = ?", history.DbInstanceId).
|
||||
Where("sequence = ?", history.Sequence).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
First(old).Error
|
||||
switch {
|
||||
case err == nil:
|
||||
return db.Model(old).Select("create_time", "file_size", "first_event_time", "last_event_time").Updates(history).Error
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
return db.Create(history).Error
|
||||
default:
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (repo *dbBinlogHistoryRepoImpl) InsertWithBinlogFiles(ctx context.Context, instanceId uint64, binlogFiles []*entity.BinlogFile) error {
|
||||
if len(binlogFiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
histories := make([]*entity.DbBinlogHistory, 0, len(binlogFiles))
|
||||
for _, fileOnServer := range binlogFiles {
|
||||
if !fileOnServer.Downloaded {
|
||||
break
|
||||
}
|
||||
history := &entity.DbBinlogHistory{
|
||||
CreateTime: time.Now(),
|
||||
FileName: fileOnServer.Name,
|
||||
FileSize: fileOnServer.RemoteSize,
|
||||
Sequence: fileOnServer.Sequence,
|
||||
FirstEventTime: fileOnServer.FirstEventTime,
|
||||
LastEventTime: fileOnServer.LastEventTime,
|
||||
DbInstanceId: instanceId,
|
||||
}
|
||||
histories = append(histories, history)
|
||||
}
|
||||
if len(histories) > 1 {
|
||||
if err := repo.BatchInsert(ctx, histories[:len(histories)-1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(histories) > 0 {
|
||||
if err := repo.Upsert(ctx, histories[len(histories)-1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *dbBinlogHistoryRepoImpl) GetHistoriesBeforeSequence(ctx context.Context, instanceId uint64, binlogSeq int64, histories *[]*entity.DbBinlogHistory) error {
|
||||
return global.Db.Model(repo.NewModel()).
|
||||
Where("db_instance_id = ?", instanceId).
|
||||
Where("sequence < ?", binlogSeq).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Order("id").
|
||||
Find(histories).
|
||||
Error
|
||||
}
|
||||
@@ -16,24 +16,24 @@ func newDataSyncTaskRepo() repository.DataSyncTask {
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dataSyncTaskRepoImpl) GetTaskList(condition *entity.DataSyncTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (d *dataSyncTaskRepoImpl) GetTaskList(condition *entity.DataSyncTaskQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncTask], error) {
|
||||
qd := model.NewCond().
|
||||
Like("task_name", condition.Name).
|
||||
Eq("status", condition.Status)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
return d.PageByCond(qd, condition.PageParam)
|
||||
}
|
||||
|
||||
type dataSyncLogRepoImpl struct {
|
||||
base.RepoImpl[*entity.DataSyncLog]
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dataSyncLogRepoImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("task_id", condition.TaskId)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
func newDataSyncLogRepo() repository.DataSyncLog {
|
||||
return &dataSyncLogRepoImpl{}
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dataSyncLogRepoImpl) GetTaskLogList(condition *entity.DataSyncLogQuery, orderBy ...string) (*model.PageResult[*entity.DataSyncLog], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("task_id", condition.TaskId)
|
||||
return d.PageByCond(qd, condition.PageParam)
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"reflect"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ repository.DbJobBase[entity.DbJob] = (*dbJobBaseImpl[entity.DbJob])(nil)
|
||||
|
||||
type dbJobBaseImpl[T entity.DbJob] struct {
|
||||
base.RepoImpl[T]
|
||||
}
|
||||
|
||||
func (d *dbJobBaseImpl[T]) UpdateLastStatus(ctx context.Context, job entity.DbJob) error {
|
||||
return d.UpdateById(ctx, job.(T), "last_status", "last_result", "last_time")
|
||||
}
|
||||
|
||||
func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any) error {
|
||||
// refactor jobs from any to []T
|
||||
return gormx.Tx(func(db *gorm.DB) error {
|
||||
var instanceId uint64
|
||||
var dbNames []string
|
||||
reflectValue := reflect.ValueOf(jobs)
|
||||
var plural bool
|
||||
switch reflectValue.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
plural = true
|
||||
reflectLen := reflectValue.Len()
|
||||
dbNames = make([]string, 0, reflectLen)
|
||||
for i := 0; i < reflectLen; i++ {
|
||||
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
||||
if instanceId == 0 {
|
||||
instanceId = job.GetInstanceId()
|
||||
}
|
||||
if job.GetInstanceId() != instanceId {
|
||||
return errors.New("不支持同时为多个数据库实例添加数据库任务")
|
||||
}
|
||||
if job.GetInterval() == 0 {
|
||||
// 单次执行的数据库任务可重复创建
|
||||
continue
|
||||
}
|
||||
dbNames = append(dbNames, job.GetDbName())
|
||||
}
|
||||
default:
|
||||
job := jobs.(entity.DbJob)
|
||||
instanceId = job.GetInstanceId()
|
||||
if job.GetInterval() > 0 {
|
||||
dbNames = append(dbNames, job.GetDbName())
|
||||
}
|
||||
}
|
||||
|
||||
var res []string
|
||||
err := db.Model(repo.NewModel()).Select("db_name").
|
||||
Where("db_instance_id = ?", instanceId).
|
||||
Where("db_name in ?", dbNames).
|
||||
Where("repeated = true").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Find(&res).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(res) > 0 {
|
||||
return errors.New(fmt.Sprintf("数据库任务已存在: %v", res))
|
||||
}
|
||||
if plural {
|
||||
return repo.BatchInsertWithDb(ctx, db, jobs.([]T))
|
||||
}
|
||||
return repo.InsertWithDb(ctx, db, jobs.(T))
|
||||
})
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
"slices"
|
||||
)
|
||||
|
||||
var _ repository.DbRestore = (*dbRestoreRepoImpl)(nil)
|
||||
|
||||
type dbRestoreRepoImpl struct {
|
||||
dbJobBaseImpl[*entity.DbRestore]
|
||||
}
|
||||
|
||||
func NewDbRestoreRepo() repository.DbRestore {
|
||||
return &dbRestoreRepoImpl{}
|
||||
}
|
||||
|
||||
func (d *dbRestoreRepoImpl) GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error) {
|
||||
var dbNamesWithRestore []string
|
||||
err := global.Db.Model(d.NewModel()).
|
||||
Where("db_instance_id = ?", instanceId).
|
||||
Where("repeated = ?", true).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Pluck("db_name", &dbNamesWithRestore).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(dbNames))
|
||||
for _, name := range dbNames {
|
||||
if !slices.Contains(dbNamesWithRestore, name) {
|
||||
result = append(result, name)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *dbRestoreRepoImpl) ListToDo(jobs any) error {
|
||||
db := global.Db.Model(d.NewModel())
|
||||
err := db.Where("enabled = ?", true).
|
||||
Where(db.Where("repeated = ?", true).Or("last_status <> ?", entity.DbJobSuccess)).
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Find(jobs).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageList 分页获取数据库备份任务列表
|
||||
func (d *dbRestoreRepoImpl) GetPageList(condition *entity.DbRestoreQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("id", condition.Id).
|
||||
Eq0("db_instance_id", condition.DbInstanceId).
|
||||
Eq0("repeated", condition.Repeated).
|
||||
In0("db_name", condition.InDbNames).
|
||||
Like("db_name", condition.DbName)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
func (d *dbRestoreRepoImpl) GetEnabledRestores(toEntity any, backupHistoryId ...uint64) error {
|
||||
return global.Db.Model(d.NewModel()).
|
||||
Select("id", "db_backup_history_id", "last_status", "last_result", "last_time").
|
||||
Where("db_backup_history_id in ?", backupHistoryId).
|
||||
Where("enabled = true").
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Order("id DESC").
|
||||
Find(toEntity).
|
||||
Error
|
||||
}
|
||||
|
||||
// AddJob 添加数据库任务
|
||||
func (d *dbRestoreRepoImpl) AddJob(ctx context.Context, jobs any) error {
|
||||
return addJob[*entity.DbRestore](ctx, d.dbJobBaseImpl, jobs)
|
||||
}
|
||||
|
||||
func (d *dbRestoreRepoImpl) UpdateEnabled(ctx context.Context, jobId uint64, enabled bool) error {
|
||||
cond := map[string]any{
|
||||
"id": jobId,
|
||||
}
|
||||
desc := "已禁用"
|
||||
if enabled {
|
||||
desc = "已启用"
|
||||
}
|
||||
return d.UpdateByCond(ctx, map[string]any{
|
||||
"enabled": enabled,
|
||||
"enabled_desc": desc,
|
||||
}, cond)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
var _ repository.DbRestoreHistory = (*dbRestoreHistoryRepoImpl)(nil)
|
||||
|
||||
type dbRestoreHistoryRepoImpl struct {
|
||||
base.RepoImpl[*entity.DbRestoreHistory]
|
||||
}
|
||||
|
||||
func NewDbRestoreHistoryRepo() repository.DbRestoreHistory {
|
||||
return &dbRestoreHistoryRepoImpl{}
|
||||
}
|
||||
|
||||
func (d *dbRestoreHistoryRepoImpl) GetDbRestoreHistories(condition *entity.DbRestoreHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("id", condition.Id).
|
||||
Eq("db_backup_id", condition.DbRestoreId)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ func newDbSqlExecRepo() repository.DbSqlExec {
|
||||
}
|
||||
|
||||
// 分页获取
|
||||
func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, orderBy ...string) (*model.PageResult[*entity.DbSqlExec], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("db_id", condition.DbId).
|
||||
Eq("`table`", condition.Table).
|
||||
@@ -25,5 +25,5 @@ func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pagePa
|
||||
Eq("flow_biz_key", condition.FlowBizKey).
|
||||
In("status", condition.Status).
|
||||
RLike("db", condition.Db).OrderBy(orderBy...)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
return d.PageByCond(qd, condition.PageParam)
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ func newDbTransferTaskRepo() repository.DbTransferTask {
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbTransferTaskRepoImpl) GetTaskList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (d *dbTransferTaskRepoImpl) GetTaskList(condition *entity.DbTransferTaskQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferTask], error) {
|
||||
qd := model.NewCond().
|
||||
Like("task_name", condition.Name).
|
||||
Eq("status", condition.Status).
|
||||
Eq("cron_able", condition.CronAble)
|
||||
//Eq("status", condition.Status)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
return d.PageByCond(qd, condition.PageParam)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ func newDbTransferFileRepo() repository.DbTransferFile {
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbTransferFileRepoImpl) GetPageList(condition *entity.DbTransferFileQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (d *dbTransferFileRepoImpl) GetPageList(condition *entity.DbTransferFileQuery, orderBy ...string) (*model.PageResult[*entity.DbTransferFile], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("task_id", condition.TaskId).
|
||||
OrderByDesc("create_time")
|
||||
//Eq("status", condition.Status)
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
return d.PageByCond(qd, condition.PageParam)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func NewInstanceRepo() repository.Instance {
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *instanceRepoImpl) GetInstanceList(condition *entity.InstanceQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (d *instanceRepoImpl) GetInstanceList(condition *entity.InstanceQuery, orderBy ...string) (*model.PageResult[*entity.DbInstance], error) {
|
||||
qd := model.NewCond().
|
||||
Eq("id", condition.Id).
|
||||
Eq("host", condition.Host).
|
||||
@@ -30,5 +30,5 @@ func (d *instanceRepoImpl) GetInstanceList(condition *entity.InstanceQuery, page
|
||||
qd.And("host like ? or name like ? or code like ?", keyword, keyword, keyword)
|
||||
}
|
||||
|
||||
return d.PageByCondToAny(qd, pageParam, toEntity)
|
||||
return d.PageByCond(qd, condition.PageParam)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,4 @@ func InitIoc() {
|
||||
ioc.Register(newDataSyncLogRepo(), ioc.WithComponentName("DbDataSyncLogRepo"))
|
||||
ioc.Register(newDbTransferTaskRepo(), ioc.WithComponentName("DbTransferTaskRepo"))
|
||||
ioc.Register(newDbTransferFileRepo(), ioc.WithComponentName("DbTransferFileRepo"))
|
||||
|
||||
ioc.Register(NewDbBackupRepo(), ioc.WithComponentName("DbBackupRepo"))
|
||||
ioc.Register(NewDbBackupHistoryRepo(), ioc.WithComponentName("DbBackupHistoryRepo"))
|
||||
ioc.Register(NewDbRestoreRepo(), ioc.WithComponentName("DbRestoreRepo"))
|
||||
ioc.Register(NewDbRestoreHistoryRepo(), ioc.WithComponentName("DbRestoreHistoryRepo"))
|
||||
ioc.Register(NewDbBinlogRepo(), ioc.WithComponentName("DbBinlogRepo"))
|
||||
ioc.Register(NewDbBinlogHistoryRepo(), ioc.WithComponentName("DbBinlogHistoryRepo"))
|
||||
}
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
package init
|
||||
|
||||
import "mayfly-go/internal/db/application"
|
||||
|
||||
// 终止进程时的处理函数
|
||||
func Terminate() {
|
||||
closeDbTasks()
|
||||
}
|
||||
|
||||
func closeDbTasks() {
|
||||
restoreApp := application.GetDbRestoreApp()
|
||||
if restoreApp != nil {
|
||||
restoreApp.Close()
|
||||
}
|
||||
binlogApp := application.GetDbBinlogApp()
|
||||
if binlogApp != nil {
|
||||
binlogApp.Close()
|
||||
}
|
||||
backupApp := application.GetDbBackupApp()
|
||||
if backupApp != nil {
|
||||
backupApp.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user