mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-04-19 10:45:19 +08:00
!84 fix: 修复数据库备份与恢复问题
* refactor dbScheduler * fix: 按团队名称检索团队 * feat: 创建数据库资源时支持全选数据库 * refactor dbScheduler * fix: 修复数据库备份与恢复问题
This commit is contained in:
@@ -54,9 +54,14 @@ func (d *DbBackup) Create(rc *req.Ctx) {
|
||||
jobs := make([]*entity.DbBackup, 0, len(dbNames))
|
||||
for _, dbName := range dbNames {
|
||||
job := &entity.DbBackup{
|
||||
DbJobBaseImpl: entity.NewDbBJobBase(db.InstanceId, dbName, entity.DbJobTypeBackup, true, backupForm.Repeated, backupForm.StartTime, backupForm.Interval),
|
||||
DbJobBaseImpl: entity.NewDbBJobBase(db.InstanceId, entity.DbJobTypeBackup),
|
||||
Enabled: true,
|
||||
Repeated: backupForm.Repeated,
|
||||
StartTime: backupForm.StartTime,
|
||||
Interval: backupForm.Interval,
|
||||
Name: backupForm.Name,
|
||||
}
|
||||
job.DbName = dbName
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
biz.ErrIsNilAppendErr(d.DbBackupApp.Create(rc.MetaCtx, jobs), "添加数据库备份任务失败: %v")
|
||||
|
||||
@@ -48,12 +48,17 @@ func (d *DbRestore) Create(rc *req.Ctx) {
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||
|
||||
job := &entity.DbRestore{
|
||||
DbJobBaseImpl: entity.NewDbBJobBase(db.InstanceId, restoreForm.DbName, entity.DbJobTypeRestore, true, restoreForm.Repeated, restoreForm.StartTime, restoreForm.Interval),
|
||||
DbJobBaseImpl: entity.NewDbBJobBase(db.InstanceId, entity.DbJobTypeRestore),
|
||||
Enabled: true,
|
||||
Repeated: restoreForm.Repeated,
|
||||
StartTime: restoreForm.StartTime,
|
||||
Interval: restoreForm.Interval,
|
||||
PointInTime: restoreForm.PointInTime,
|
||||
DbBackupId: restoreForm.DbBackupId,
|
||||
DbBackupHistoryId: restoreForm.DbBackupHistoryId,
|
||||
DbBackupHistoryName: restoreForm.DbBackupHistoryName,
|
||||
}
|
||||
job.DbName = restoreForm.DbName
|
||||
biz.ErrIsNilAppendErr(d.DbRestoreApp.Create(rc.MetaCtx, job), "添加数据库恢复任务失败: %v")
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ func Init() {
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("初始化 dbRestoreApp 失败: %v", err))
|
||||
}
|
||||
dbBinlogApp, err = newDbBinlogApp(repositories, dbApp)
|
||||
dbBinlogApp, err = newDbBinlogApp(repositories, dbApp, scheduler)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("初始化 dbBinlogApp 失败: %v", err))
|
||||
}
|
||||
|
||||
@@ -2,20 +2,14 @@ package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
binlogDownloadInterval = time.Minute * 15
|
||||
)
|
||||
|
||||
type DbBinlogApp struct {
|
||||
binlogRepo repository.DbBinlog
|
||||
binlogHistoryRepo repository.DbBinlogHistory
|
||||
@@ -25,19 +19,10 @@ type DbBinlogApp struct {
|
||||
context context.Context
|
||||
cancel context.CancelFunc
|
||||
waitGroup sync.WaitGroup
|
||||
scheduler *dbScheduler
|
||||
}
|
||||
|
||||
var (
|
||||
binlogResult = map[entity.DbJobStatus]string{
|
||||
entity.DbJobDelay: "等待备份BINLOG",
|
||||
entity.DbJobReady: "准备备份BINLOG",
|
||||
entity.DbJobRunning: "BINLOG备份中",
|
||||
entity.DbJobSuccess: "BINLOG备份成功",
|
||||
entity.DbJobFailed: "BINLOG备份失败",
|
||||
}
|
||||
)
|
||||
|
||||
func newDbBinlogApp(repositories *repository.Repositories, dbApp Db) (*DbBinlogApp, error) {
|
||||
func newDbBinlogApp(repositories *repository.Repositories, dbApp Db, scheduler *dbScheduler) (*DbBinlogApp, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
svc := &DbBinlogApp{
|
||||
binlogRepo: repositories.Binlog,
|
||||
@@ -45,6 +30,7 @@ func newDbBinlogApp(repositories *repository.Repositories, dbApp Db) (*DbBinlogA
|
||||
backupRepo: repositories.Backup,
|
||||
backupHistoryRepo: repositories.BackupHistory,
|
||||
dbApp: dbApp,
|
||||
scheduler: scheduler,
|
||||
context: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
@@ -53,73 +39,47 @@ func newDbBinlogApp(repositories *repository.Repositories, dbApp Db) (*DbBinlogA
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) fetchBinlog(ctx context.Context, backup *entity.DbBackup) error {
|
||||
if err := app.AddJobIfNotExists(ctx, entity.NewDbBinlog(backup.DbInstanceId)); err != nil {
|
||||
return err
|
||||
}
|
||||
latestBinlogSequence, earliestBackupSequence := int64(-1), int64(-1)
|
||||
binlogHistory, ok, err := app.binlogHistoryRepo.GetLatestHistory(backup.DbInstanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
latestBinlogSequence = binlogHistory.Sequence
|
||||
} else {
|
||||
backupHistory, ok, err := app.backupHistoryRepo.GetEarliestHistory(backup.DbInstanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
earliestBackupSequence = backupHistory.BinlogSequence
|
||||
}
|
||||
conn, err := app.dbApp.GetDbConnByInstanceId(backup.DbInstanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbProgram := conn.GetDialect().GetDbProgram()
|
||||
binlogFiles, err := dbProgram.FetchBinlogs(ctx, false, earliestBackupSequence, latestBinlogSequence)
|
||||
if err == nil {
|
||||
err = app.binlogHistoryRepo.InsertWithBinlogFiles(ctx, backup.DbInstanceId, binlogFiles)
|
||||
}
|
||||
jobStatus := entity.DbJobSuccess
|
||||
if err != nil {
|
||||
jobStatus = entity.DbJobFailed
|
||||
}
|
||||
job := &entity.DbBinlog{}
|
||||
job.Id = backup.DbInstanceId
|
||||
return app.updateCurJob(ctx, jobStatus, err, job)
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) run() {
|
||||
defer app.waitGroup.Done()
|
||||
|
||||
// todo: 实现 binlog 并发下载
|
||||
timex.SleepWithContext(app.context, time.Minute)
|
||||
for !app.closed() {
|
||||
app.fetchFromAllInstances()
|
||||
timex.SleepWithContext(app.context, binlogDownloadInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) fetchFromAllInstances() {
|
||||
var backups []*entity.DbBackup
|
||||
if err := app.backupRepo.ListRepeating(&backups); err != nil {
|
||||
logx.Errorf("DbBinlogApp: 获取数据库备份任务失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
for _, backup := range backups {
|
||||
jobs, err := app.loadJobs()
|
||||
if err != nil {
|
||||
logx.Errorf("DbBinlogApp: 加载 BINLOG 同步任务失败: %s", err.Error())
|
||||
timex.SleepWithContext(app.context, time.Minute)
|
||||
continue
|
||||
}
|
||||
if app.closed() {
|
||||
break
|
||||
}
|
||||
if err := app.fetchBinlog(app.context, backup); err != nil {
|
||||
logx.Errorf("DbBinlogApp: 下载 binlog 文件失败: %s", err.Error())
|
||||
return
|
||||
if err := app.scheduler.AddJob(app.context, false, entity.DbJobTypeBinlog, jobs); err != nil {
|
||||
logx.Error("DbBinlogApp: 添加 BINLOG 同步任务失败: ", err.Error())
|
||||
}
|
||||
timex.SleepWithContext(app.context, entity.BinlogDownloadInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) loadJobs() ([]*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 app.closed() {
|
||||
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() {
|
||||
app.cancel()
|
||||
app.waitGroup.Wait()
|
||||
@@ -146,14 +106,3 @@ func (app *DbBinlogApp) Delete(ctx context.Context, jobId uint64) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *DbBinlogApp) updateCurJob(ctx context.Context, status entity.DbJobStatus, lastErr error, job *entity.DbBinlog) error {
|
||||
job.LastStatus = status
|
||||
var result = binlogResult[status]
|
||||
if lastErr != nil {
|
||||
result = fmt.Sprintf("%v: %v", binlogResult[status], lastErr)
|
||||
}
|
||||
job.LastResult = stringx.TruncateStr(result, entity.LastResultSize)
|
||||
job.LastTime = timex.NewNullTime(time.Now())
|
||||
return app.binlogRepo.UpdateById(ctx, job, "last_status", "last_result", "last_time")
|
||||
}
|
||||
|
||||
@@ -26,28 +26,40 @@ type dbScheduler struct {
|
||||
backupHistoryRepo repository.DbBackupHistory
|
||||
restoreRepo repository.DbRestore
|
||||
restoreHistoryRepo repository.DbRestoreHistory
|
||||
binlogRepo repository.DbBinlog
|
||||
binlogHistoryRepo repository.DbBinlogHistory
|
||||
binlogTimes map[uint64]time.Time
|
||||
}
|
||||
|
||||
func newDbScheduler(repositories *repository.Repositories) (*dbScheduler, error) {
|
||||
scheduler := &dbScheduler{
|
||||
runner: runner.NewRunner[entity.DbJob](maxRunning),
|
||||
dbApp: dbApp,
|
||||
backupRepo: repositories.Backup,
|
||||
backupHistoryRepo: repositories.BackupHistory,
|
||||
restoreRepo: repositories.Restore,
|
||||
restoreHistoryRepo: repositories.RestoreHistory,
|
||||
binlogRepo: repositories.Binlog,
|
||||
binlogHistoryRepo: repositories.BinlogHistory,
|
||||
}
|
||||
scheduler.runner = runner.NewRunner[entity.DbJob](maxRunning, scheduler.runJob,
|
||||
runner.WithScheduleJob[entity.DbJob](scheduler.scheduleJob),
|
||||
runner.WithRunnableJob[entity.DbJob](scheduler.runnableJob),
|
||||
)
|
||||
return scheduler, nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) scheduleJob(job entity.DbJob) (time.Time, error) {
|
||||
return job.Schedule()
|
||||
}
|
||||
|
||||
func (s *dbScheduler) repo(typ entity.DbJobType) repository.DbJob {
|
||||
switch typ {
|
||||
case entity.DbJobTypeBackup:
|
||||
return s.backupRepo
|
||||
case entity.DbJobTypeRestore:
|
||||
return s.restoreRepo
|
||||
case entity.DbJobTypeBinlog:
|
||||
return s.binlogRepo
|
||||
default:
|
||||
panic(errors.New(fmt.Sprintf("无效的数据库任务类型: %v", typ)))
|
||||
}
|
||||
@@ -60,8 +72,6 @@ func (s *dbScheduler) UpdateJob(ctx context.Context, job entity.DbJob) error {
|
||||
if err := s.repo(job.GetJobType()).UpdateById(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
job.SetRun(s.run)
|
||||
job.SetRunnable(s.runnable)
|
||||
_ = s.runner.UpdateOrAdd(ctx, job)
|
||||
return nil
|
||||
}
|
||||
@@ -87,21 +97,11 @@ func (s *dbScheduler) AddJob(ctx context.Context, saving bool, jobType entity.Db
|
||||
for i := 0; i < reflectLen; i++ {
|
||||
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
||||
job.SetJobType(jobType)
|
||||
if !job.Schedule() {
|
||||
continue
|
||||
}
|
||||
job.SetRun(s.run)
|
||||
job.SetRunnable(s.runnable)
|
||||
_ = s.runner.Add(ctx, job)
|
||||
}
|
||||
default:
|
||||
job := jobs.(entity.DbJob)
|
||||
job.SetJobType(jobType)
|
||||
if !job.Schedule() {
|
||||
return nil
|
||||
}
|
||||
job.SetRun(s.run)
|
||||
job.SetRunnable(s.runnable)
|
||||
_ = s.runner.Add(ctx, job)
|
||||
}
|
||||
return nil
|
||||
@@ -131,12 +131,10 @@ func (s *dbScheduler) EnableJob(ctx context.Context, jobType entity.DbJobType, j
|
||||
if job.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
job.GetJobBase().Enabled = true
|
||||
job.SetEnabled(true)
|
||||
if err := repo.UpdateEnabled(ctx, jobId, true); err != nil {
|
||||
return err
|
||||
}
|
||||
job.SetRun(s.run)
|
||||
job.SetRunnable(s.runnable)
|
||||
_ = s.runner.Add(ctx, job)
|
||||
return nil
|
||||
}
|
||||
@@ -171,9 +169,6 @@ func (s *dbScheduler) StartJobNow(ctx context.Context, jobType entity.DbJobType,
|
||||
if !job.IsEnabled() {
|
||||
return errors.New("任务未启用")
|
||||
}
|
||||
job.GetJobBase().Deadline = time.Now()
|
||||
job.SetRun(s.run)
|
||||
job.SetRunnable(s.runnable)
|
||||
_ = s.runner.StartNow(ctx, job)
|
||||
return nil
|
||||
}
|
||||
@@ -267,7 +262,7 @@ func (s *dbScheduler) restoreMysql(ctx context.Context, job entity.DbJob) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dbScheduler) run(ctx context.Context, job entity.DbJob) {
|
||||
func (s *dbScheduler) runJob(ctx context.Context, job entity.DbJob) {
|
||||
job.SetLastStatus(entity.DbJobRunning, nil)
|
||||
if err := s.repo(job.GetJobType()).UpdateLastStatus(ctx, job); err != nil {
|
||||
logx.Errorf("failed to update job status: %v", err)
|
||||
@@ -280,6 +275,8 @@ func (s *dbScheduler) run(ctx context.Context, job entity.DbJob) {
|
||||
errRun = s.backupMysql(ctx, job)
|
||||
case entity.DbJobTypeRestore:
|
||||
errRun = s.restoreMysql(ctx, job)
|
||||
case entity.DbJobTypeBinlog:
|
||||
errRun = s.fetchBinlogMysql(ctx, job)
|
||||
default:
|
||||
errRun = errors.New(fmt.Sprintf("无效的数据库任务类型: %v", typ))
|
||||
}
|
||||
@@ -294,19 +291,27 @@ func (s *dbScheduler) run(ctx context.Context, job entity.DbJob) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *dbScheduler) runnable(job entity.DbJob, next runner.NextFunc) bool {
|
||||
func (s *dbScheduler) runnableJob(job entity.DbJob, next runner.NextJobFunc[entity.DbJob]) bool {
|
||||
const maxCountByInstanceId = 4
|
||||
const maxCountByDbName = 1
|
||||
var countByInstanceId, countByDbName int
|
||||
jobBase := job.GetJobBase()
|
||||
for item, ok := next(); ok; item, ok = next() {
|
||||
itemBase := item.(entity.DbJob).GetJobBase()
|
||||
itemBase := item.GetJobBase()
|
||||
if jobBase.DbInstanceId == itemBase.DbInstanceId {
|
||||
countByInstanceId++
|
||||
if countByInstanceId >= maxCountByInstanceId {
|
||||
return false
|
||||
}
|
||||
if jobBase.DbName == itemBase.DbName {
|
||||
|
||||
if relatedToBinlog(job.GetJobType()) {
|
||||
// todo: 恢复数据库前触发 BINLOG 同步,BINLOG 同步完成后才能恢复数据库
|
||||
if relatedToBinlog(item.GetJobType()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if job.GetDbName() == item.GetDbName() {
|
||||
countByDbName++
|
||||
if countByDbName >= maxCountByDbName {
|
||||
return false
|
||||
@@ -317,6 +322,10 @@ func (s *dbScheduler) runnable(job entity.DbJob, next runner.NextFunc) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func relatedToBinlog(typ entity.DbJobType) bool {
|
||||
return typ == entity.DbJobTypeRestore || typ == entity.DbJobTypeBinlog
|
||||
}
|
||||
|
||||
func (s *dbScheduler) restorePointInTime(ctx context.Context, program dbi.DbProgram, job *entity.DbRestore) error {
|
||||
binlogHistory, err := s.binlogHistoryRepo.GetHistoryByTime(job.DbInstanceId, job.PointInTime.Time)
|
||||
if err != nil {
|
||||
@@ -364,3 +373,34 @@ func (s *dbScheduler) restoreBackupHistory(ctx context.Context, program dbi.DbPr
|
||||
}
|
||||
return program.RestoreBackupHistory(ctx, backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid)
|
||||
}
|
||||
|
||||
func (s *dbScheduler) fetchBinlogMysql(ctx context.Context, backup entity.DbJob) error {
|
||||
instanceId := backup.GetJobBase().DbInstanceId
|
||||
latestBinlogSequence, earliestBackupSequence := int64(-1), int64(-1)
|
||||
binlogHistory, ok, err := s.binlogHistoryRepo.GetLatestHistory(instanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
latestBinlogSequence = binlogHistory.Sequence
|
||||
} else {
|
||||
backupHistory, ok, err := s.backupHistoryRepo.GetEarliestHistory(instanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
earliestBackupSequence = backupHistory.BinlogSequence
|
||||
}
|
||||
conn, err := s.dbApp.GetDbConnByInstanceId(instanceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbProgram := conn.GetDialect().GetDbProgram()
|
||||
binlogFiles, err := dbProgram.FetchBinlogs(ctx, false, earliestBackupSequence, latestBinlogSequence)
|
||||
if err == nil {
|
||||
err = s.binlogHistoryRepo.InsertWithBinlogFiles(ctx, instanceId, binlogFiles)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
@@ -186,6 +184,12 @@ func (svc *DbProgramMysql) downloadBinlogFilesOnServer(ctx context.Context, binl
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the first binlog eventTs of a local binlog file.
|
||||
func (svc *DbProgramMysql) parseLocalBinlogLastEventTime(ctx context.Context, filePath string) (eventTime time.Time, parseErr error) {
|
||||
// todo: implement me
|
||||
return time.Now(), nil
|
||||
}
|
||||
|
||||
// Parse the first binlog eventTs of a local binlog file.
|
||||
func (svc *DbProgramMysql) parseLocalBinlogFirstEventTime(ctx context.Context, filePath string) (eventTime time.Time, parseErr error) {
|
||||
args := []string{
|
||||
@@ -227,36 +231,8 @@ func (svc *DbProgramMysql) parseLocalBinlogFirstEventTime(ctx context.Context, f
|
||||
return time.Time{}, errors.New("解析 binlog 文件失败")
|
||||
}
|
||||
|
||||
var singleFlightGroup singleflight.Group
|
||||
|
||||
// FetchBinlogs downloads binlog files from startingFileName on server to `binlogDir`.
|
||||
func (svc *DbProgramMysql) FetchBinlogs(ctx context.Context, downloadLatestBinlogFile bool, earliestBackupSequence, latestBinlogSequence int64) ([]*entity.BinlogFile, error) {
|
||||
var downloaded bool
|
||||
key := strconv.FormatUint(svc.dbInfo().InstanceId, 16)
|
||||
binlogFiles, err, _ := singleFlightGroup.Do(key, func() (interface{}, error) {
|
||||
downloaded = true
|
||||
return svc.fetchBinlogs(ctx, downloadLatestBinlogFile, earliestBackupSequence, latestBinlogSequence)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if downloaded {
|
||||
return binlogFiles.([]*entity.BinlogFile), nil
|
||||
}
|
||||
if !downloadLatestBinlogFile {
|
||||
return nil, nil
|
||||
}
|
||||
binlogFiles, err, _ = singleFlightGroup.Do(key, func() (interface{}, error) {
|
||||
return svc.fetchBinlogs(ctx, true, earliestBackupSequence, latestBinlogSequence)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return binlogFiles.([]*entity.BinlogFile), err
|
||||
}
|
||||
|
||||
// fetchBinlogs downloads binlog files from startingFileName on server to `binlogDir`.
|
||||
func (svc *DbProgramMysql) fetchBinlogs(ctx context.Context, downloadLatestBinlogFile bool, earliestBackupSequence, latestBinlogSequence int64) ([]*entity.BinlogFile, error) {
|
||||
// Read binlog files list on server.
|
||||
binlogFilesOnServerSorted, err := svc.GetSortedBinlogFilesOnServer(ctx)
|
||||
if err != nil {
|
||||
@@ -352,7 +328,13 @@ func (svc *DbProgramMysql) downloadBinlogFile(ctx context.Context, binlogFileToD
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lastEventTime, err := svc.parseLocalBinlogLastEventTime(ctx, binlogFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
binlogFileToDownload.FirstEventTime = firstEventTime
|
||||
binlogFileToDownload.LastEventTime = lastEventTime
|
||||
binlogFileToDownload.Downloaded = true
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/internal/db/infrastructure/persistence"
|
||||
@@ -32,21 +33,21 @@ type DbInstanceSuite struct {
|
||||
suite.Suite
|
||||
repositories *repository.Repositories
|
||||
instanceSvc *DbProgramMysql
|
||||
dbConn *DbConn
|
||||
dbConn *dbi.DbConn
|
||||
}
|
||||
|
||||
func (s *DbInstanceSuite) SetupSuite() {
|
||||
if err := chdir("mayfly-go", "server"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dbInfo := DbInfo{
|
||||
Type: DbTypeMysql,
|
||||
dbInfo := dbi.DbInfo{
|
||||
Type: dbi.DbTypeMysql,
|
||||
Host: "localhost",
|
||||
Port: 3306,
|
||||
Username: "test",
|
||||
Password: "test",
|
||||
}
|
||||
dbConn, err := dbInfo.Conn()
|
||||
dbConn, err := dbInfo.Conn(GetMeta())
|
||||
s.Require().NoError(err)
|
||||
s.dbConn = dbConn
|
||||
s.repositories = &repository.Repositories{
|
||||
@@ -203,7 +204,7 @@ func (s *DbInstanceSuite) testReplayBinlog(backupHistory *entity.DbBackupHistory
|
||||
binlogHistories = append(binlogHistories, binlogHistoryLast)
|
||||
}
|
||||
|
||||
restoreInfo := &RestoreInfo{
|
||||
restoreInfo := &dbi.RestoreInfo{
|
||||
BackupHistory: backupHistory,
|
||||
BinlogHistories: binlogHistories,
|
||||
StartPosition: backupHistory.BinlogPosition,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/pkg/runner"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ DbJob = (*DbBackup)(nil)
|
||||
@@ -11,17 +11,56 @@ var _ DbJob = (*DbBackup)(nil)
|
||||
type DbBackup struct {
|
||||
*DbJobBaseImpl
|
||||
|
||||
Name string `json:"Name"` // 数据库备份名称
|
||||
Enabled bool // 是否启用
|
||||
StartTime time.Time // 开始时间
|
||||
Interval time.Duration // 间隔时间
|
||||
Repeated bool // 是否重复执行
|
||||
DbName string // 数据库名称
|
||||
Name string // 数据库备份名称
|
||||
}
|
||||
|
||||
func (d *DbBackup) SetRun(fn func(ctx context.Context, job DbJob)) {
|
||||
d.run = func(ctx context.Context) {
|
||||
fn(ctx, d)
|
||||
}
|
||||
func (b *DbBackup) GetDbName() string {
|
||||
return b.DbName
|
||||
}
|
||||
|
||||
func (d *DbBackup) SetRunnable(fn func(job DbJob, next runner.NextFunc) bool) {
|
||||
d.runnable = func(next runner.NextFunc) bool {
|
||||
return fn(d, next)
|
||||
func (b *DbBackup) Schedule() (time.Time, error) {
|
||||
var deadline time.Time
|
||||
if b.IsFinished() || !b.Enabled {
|
||||
return deadline, runner.ErrFinished
|
||||
}
|
||||
switch b.LastStatus {
|
||||
case DbJobSuccess:
|
||||
lastTime := b.LastTime.Time
|
||||
if lastTime.Before(b.StartTime) {
|
||||
lastTime = b.StartTime.Add(-b.Interval)
|
||||
}
|
||||
deadline = lastTime.Add(b.Interval - lastTime.Sub(b.StartTime)%b.Interval)
|
||||
case DbJobFailed:
|
||||
deadline = time.Now().Add(time.Minute)
|
||||
default:
|
||||
deadline = b.StartTime
|
||||
}
|
||||
return deadline, nil
|
||||
}
|
||||
|
||||
func (b *DbBackup) IsFinished() bool {
|
||||
return !b.Repeated && b.LastStatus == DbJobSuccess
|
||||
}
|
||||
|
||||
func (b *DbBackup) IsEnabled() bool {
|
||||
return b.Enabled
|
||||
}
|
||||
|
||||
func (b *DbBackup) SetEnabled(enabled bool) {
|
||||
b.Enabled = enabled
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"mayfly-go/pkg/runner"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DbBinlog 数据库备份任务
|
||||
type DbBinlog struct {
|
||||
model.Model
|
||||
|
||||
LastStatus DbJobStatus // 最近一次执行状态
|
||||
LastResult string // 最近一次执行结果
|
||||
LastTime timex.NullTime // 最近一次执行时间
|
||||
DbInstanceId uint64 `json:"dbInstanceId"` // 数据库实例ID
|
||||
}
|
||||
|
||||
func NewDbBinlog(instanceId uint64) *DbBinlog {
|
||||
job := &DbBinlog{}
|
||||
job.Id = instanceId
|
||||
job.DbInstanceId = instanceId
|
||||
return job
|
||||
}
|
||||
const (
|
||||
BinlogDownloadInterval = time.Minute * 15
|
||||
)
|
||||
|
||||
// BinlogFile is the metadata of the MySQL binlog file.
|
||||
type BinlogFile struct {
|
||||
@@ -31,5 +17,49 @@ type BinlogFile struct {
|
||||
// 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
|
||||
}
|
||||
|
||||
func NewDbBinlog(instanceId uint64) *DbBinlog {
|
||||
job := &DbBinlog{}
|
||||
job.Id = instanceId
|
||||
job.DbInstanceId = instanceId
|
||||
return job
|
||||
}
|
||||
|
||||
func (b *DbBinlog) GetDbName() string {
|
||||
// binlog 是全库级别的
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *DbBinlog) Schedule() (time.Time, error) {
|
||||
switch b.GetJobBase().LastStatus {
|
||||
case DbJobSuccess:
|
||||
return time.Time{}, runner.ErrFinished
|
||||
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) SetEnabled(_ bool) {}
|
||||
|
||||
func (b *DbBinlog) GetInterval() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/runner"
|
||||
@@ -14,18 +13,11 @@ const LastResultSize = 256
|
||||
|
||||
type DbJobKey = runner.JobKey
|
||||
|
||||
type DbJobStatus = runner.JobStatus
|
||||
type DbJobStatus int
|
||||
|
||||
const (
|
||||
DbJobUnknown = runner.JobUnknown
|
||||
DbJobDelay = runner.JobDelay
|
||||
DbJobReady = runner.JobWaiting
|
||||
DbJobRunning = runner.JobRunning
|
||||
DbJobRemoved = runner.JobRemoved
|
||||
)
|
||||
|
||||
const (
|
||||
DbJobSuccess DbJobStatus = 0x20 + iota
|
||||
DbJobRunning DbJobStatus = iota
|
||||
DbJobSuccess
|
||||
DbJobFailed
|
||||
)
|
||||
|
||||
@@ -34,32 +26,37 @@ type DbJobType = string
|
||||
const (
|
||||
DbJobTypeBackup DbJobType = "db-backup"
|
||||
DbJobTypeRestore DbJobType = "db-restore"
|
||||
DbJobTypeBinlog DbJobType = "db-binlog"
|
||||
)
|
||||
|
||||
const (
|
||||
DbJobNameBackup = "数据库备份"
|
||||
DbJobNameRestore = "数据库恢复"
|
||||
DbJobNameBinlog = "BINLOG同步"
|
||||
)
|
||||
|
||||
var _ runner.Job = (DbJob)(nil)
|
||||
|
||||
type DbJobBase interface {
|
||||
model.ModelI
|
||||
runner.Job
|
||||
|
||||
GetId() uint64
|
||||
GetKey() string
|
||||
GetJobType() DbJobType
|
||||
SetJobType(typ DbJobType)
|
||||
GetJobBase() *DbJobBaseImpl
|
||||
SetLastStatus(status DbJobStatus, err error)
|
||||
IsEnabled() bool
|
||||
}
|
||||
|
||||
type DbJob interface {
|
||||
runner.Job
|
||||
DbJobBase
|
||||
|
||||
SetRun(fn func(ctx context.Context, job DbJob))
|
||||
SetRunnable(fn func(job DbJob, next runner.NextFunc) bool)
|
||||
GetDbName() string
|
||||
Schedule() (time.Time, error)
|
||||
IsEnabled() bool
|
||||
SetEnabled(enabled bool)
|
||||
Update(job runner.Job)
|
||||
GetInterval() time.Duration
|
||||
}
|
||||
|
||||
func NewDbJob(typ DbJobType) DbJob {
|
||||
@@ -85,31 +82,17 @@ type DbJobBaseImpl struct {
|
||||
model.Model
|
||||
|
||||
DbInstanceId uint64 // 数据库实例ID
|
||||
DbName string // 数据库名称
|
||||
Enabled bool // 是否启用
|
||||
StartTime time.Time // 开始时间
|
||||
Interval time.Duration // 间隔时间
|
||||
Repeated bool // 是否重复执行
|
||||
LastStatus DbJobStatus // 最近一次执行状态
|
||||
LastResult string // 最近一次执行结果
|
||||
LastTime timex.NullTime // 最近一次执行时间
|
||||
Deadline time.Time `gorm:"-" json:"-"` // 计划执行时间
|
||||
run runner.RunFunc
|
||||
runnable runner.RunnableFunc
|
||||
jobType DbJobType
|
||||
jobKey runner.JobKey
|
||||
jobStatus runner.JobStatus
|
||||
}
|
||||
|
||||
func NewDbBJobBase(instanceId uint64, dbName string, jobType DbJobType, enabled bool, repeated bool, startTime time.Time, interval time.Duration) *DbJobBaseImpl {
|
||||
func NewDbBJobBase(instanceId uint64, jobType DbJobType) *DbJobBaseImpl {
|
||||
return &DbJobBaseImpl{
|
||||
DbInstanceId: instanceId,
|
||||
DbName: dbName,
|
||||
jobType: jobType,
|
||||
Enabled: enabled,
|
||||
Repeated: repeated,
|
||||
StartTime: startTime,
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +121,8 @@ func (d *DbJobBaseImpl) SetLastStatus(status DbJobStatus, err error) {
|
||||
jobName = DbJobNameBackup
|
||||
case DbJobTypeRestore:
|
||||
jobName = DbJobNameRestore
|
||||
case DbJobNameBinlog:
|
||||
jobName = DbJobNameBinlog
|
||||
default:
|
||||
jobName = d.jobType
|
||||
}
|
||||
@@ -150,71 +135,10 @@ func (d *DbJobBaseImpl) SetLastStatus(status DbJobStatus, err error) {
|
||||
d.LastTime = timex.NewNullTime(time.Now())
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) GetId() uint64 {
|
||||
if d == nil {
|
||||
return 0
|
||||
}
|
||||
return d.Id
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) GetDeadline() time.Time {
|
||||
return d.Deadline
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) Schedule() bool {
|
||||
if d.IsFinished() || !d.Enabled {
|
||||
return false
|
||||
}
|
||||
switch d.LastStatus {
|
||||
case DbJobSuccess:
|
||||
if d.Interval == 0 {
|
||||
return false
|
||||
}
|
||||
lastTime := d.LastTime.Time
|
||||
if lastTime.Sub(d.StartTime) < 0 {
|
||||
lastTime = d.StartTime.Add(-d.Interval)
|
||||
}
|
||||
d.Deadline = lastTime.Add(d.Interval - lastTime.Sub(d.StartTime)%d.Interval)
|
||||
case DbJobFailed:
|
||||
d.Deadline = time.Now().Add(time.Minute)
|
||||
default:
|
||||
d.Deadline = d.StartTime
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) IsFinished() bool {
|
||||
return !d.Repeated && d.LastStatus == DbJobSuccess
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) Update(job runner.Job) {
|
||||
jobBase := job.(DbJob).GetJobBase()
|
||||
d.StartTime = jobBase.StartTime
|
||||
d.Interval = jobBase.Interval
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) GetJobBase() *DbJobBaseImpl {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) IsEnabled() bool {
|
||||
return d.Enabled
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) Run(ctx context.Context) {
|
||||
if d.run == nil {
|
||||
return
|
||||
}
|
||||
d.run(ctx)
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) Runnable(next runner.NextFunc) bool {
|
||||
if d.runnable == nil {
|
||||
return true
|
||||
}
|
||||
return d.runnable(next)
|
||||
}
|
||||
|
||||
func FormatJobKey(typ DbJobType, jobId uint64) DbJobKey {
|
||||
return fmt.Sprintf("%v-%d", typ, jobId)
|
||||
}
|
||||
@@ -225,11 +149,3 @@ func (d *DbJobBaseImpl) GetKey() DbJobKey {
|
||||
}
|
||||
return d.jobKey
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) GetStatus() DbJobStatus {
|
||||
return d.jobStatus
|
||||
}
|
||||
|
||||
func (d *DbJobBaseImpl) SetStatus(status DbJobStatus) {
|
||||
d.jobStatus = status
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/pkg/runner"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ DbJob = (*DbRestore)(nil)
|
||||
@@ -12,20 +12,59 @@ var _ DbJob = (*DbRestore)(nil)
|
||||
type DbRestore struct {
|
||||
*DbJobBaseImpl
|
||||
|
||||
DbName string // 数据库名称
|
||||
Enabled bool // 是否启用
|
||||
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 (d *DbRestore) SetRun(fn func(ctx context.Context, job DbJob)) {
|
||||
d.run = func(ctx context.Context) {
|
||||
fn(ctx, d)
|
||||
}
|
||||
func (r *DbRestore) GetDbName() string {
|
||||
return r.DbName
|
||||
}
|
||||
|
||||
func (d *DbRestore) SetRunnable(fn func(job DbJob, next runner.NextFunc) bool) {
|
||||
d.runnable = func(next runner.NextFunc) bool {
|
||||
return fn(d, next)
|
||||
func (r *DbRestore) Schedule() (time.Time, error) {
|
||||
var deadline time.Time
|
||||
if r.IsFinished() || !r.Enabled {
|
||||
return deadline, runner.ErrFinished
|
||||
}
|
||||
switch r.LastStatus {
|
||||
case DbJobSuccess:
|
||||
lastTime := r.LastTime.Time
|
||||
if lastTime.Before(r.StartTime) {
|
||||
lastTime = r.StartTime.Add(-r.Interval)
|
||||
}
|
||||
deadline = lastTime.Add(r.Interval - lastTime.Sub(r.StartTime)%r.Interval)
|
||||
case DbJobFailed:
|
||||
deadline = time.Now().Add(time.Minute)
|
||||
default:
|
||||
deadline = r.StartTime
|
||||
}
|
||||
return deadline, nil
|
||||
}
|
||||
|
||||
func (r *DbRestore) IsEnabled() bool {
|
||||
return r.Enabled
|
||||
}
|
||||
|
||||
func (r *DbRestore) SetEnabled(enabled bool) {
|
||||
r.Enabled = enabled
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbBackup interface {
|
||||
DbJob
|
||||
|
||||
ListToDo(jobs any) error
|
||||
ListDbInstances(enabled bool, repeated bool, instanceIds *[]uint64) error
|
||||
GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error)
|
||||
|
||||
// GetPageList 分页获取数据库任务列表
|
||||
GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type DbBinlog interface {
|
||||
base.Repo[*entity.DbBinlog]
|
||||
DbJob
|
||||
|
||||
AddJobIfNotExists(ctx context.Context, job *entity.DbBinlog) error
|
||||
}
|
||||
|
||||
@@ -2,27 +2,27 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gorm.io/gorm"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbJob interface {
|
||||
// AddJob 添加数据库任务
|
||||
AddJob(ctx context.Context, jobs any) error
|
||||
type DbJobBase interface {
|
||||
// GetById 根据实体id查询
|
||||
GetById(e entity.DbJob, id uint64, cols ...string) error
|
||||
// GetPageList 分页获取数据库任务列表
|
||||
GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
// UpdateById 根据实体id更新实体信息
|
||||
UpdateById(ctx context.Context, e entity.DbJob, columns ...string) error
|
||||
// BatchInsertWithDb 使用指定gorm db执行,主要用于事务执行
|
||||
BatchInsertWithDb(ctx context.Context, db *gorm.DB, es any) error
|
||||
|
||||
// DeleteById 根据实体主键删除实体
|
||||
DeleteById(ctx context.Context, id uint64) error
|
||||
|
||||
// UpdateLastStatus 更新任务执行状态
|
||||
UpdateLastStatus(ctx context.Context, job entity.DbJob) error
|
||||
UpdateEnabled(ctx context.Context, jobId uint64, enabled bool) error
|
||||
ListToDo(jobs any) error
|
||||
ListRepeating(jobs any) error
|
||||
}
|
||||
|
||||
type DbJob interface {
|
||||
DbJobBase
|
||||
|
||||
// AddJob 添加数据库任务
|
||||
AddJob(ctx context.Context, jobs any) error
|
||||
UpdateEnabled(ctx context.Context, jobId uint64, enabled bool) error
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type DbRestore interface {
|
||||
DbJob
|
||||
|
||||
ListToDo(jobs any) error
|
||||
GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error)
|
||||
|
||||
// GetPageList 分页获取数据库任务列表
|
||||
GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
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 {
|
||||
dbJobBase[*entity.DbBackup]
|
||||
dbJobBaseImpl[*entity.DbBackup]
|
||||
}
|
||||
|
||||
func NewDbBackupRepo() repository.DbBackup {
|
||||
@@ -21,7 +24,8 @@ func (d *dbBackupRepoImpl) GetDbNamesWithoutBackup(instanceId uint64, dbNames []
|
||||
var dbNamesWithBackup []string
|
||||
query := gormx.NewQuery(d.GetModel()).
|
||||
Eq("db_instance_id", instanceId).
|
||||
Eq("repeated", true)
|
||||
Eq("repeated", true).
|
||||
Undeleted()
|
||||
if err := query.GenGdb().Pluck("db_name", &dbNamesWithBackup).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,3 +37,49 @@ func (d *dbBackupRepoImpl) GetDbNamesWithoutBackup(instanceId uint64, dbNames []
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *dbBackupRepoImpl) ListDbInstances(enabled bool, repeated bool, instanceIds *[]uint64) error {
|
||||
query := gormx.NewQuery(d.GetModel()).
|
||||
Eq0("enabled", enabled).
|
||||
Eq0("repeated", repeated).
|
||||
Undeleted()
|
||||
return query.GenGdb().Distinct().Pluck("db_instance_id", &instanceIds).Error
|
||||
}
|
||||
|
||||
func (d *dbBackupRepoImpl) ListToDo(jobs any) error {
|
||||
db := global.Db.Model(d.GetModel())
|
||||
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.DbJobQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||
d.GetModel()
|
||||
qd := gormx.NewQuery(d.GetModel()).
|
||||
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 gormx.PageQuery(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(_ context.Context, jobId uint64, enabled bool) error {
|
||||
cond := map[string]any{
|
||||
"id": jobId,
|
||||
}
|
||||
return d.Updates(cond, map[string]any{
|
||||
"enabled": enabled,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/global"
|
||||
)
|
||||
|
||||
var _ repository.DbBinlog = (*dbBinlogRepoImpl)(nil)
|
||||
|
||||
type dbBinlogRepoImpl struct {
|
||||
base.RepoImpl[*entity.DbBinlog]
|
||||
dbJobBaseImpl[*entity.DbBinlog]
|
||||
}
|
||||
|
||||
func NewDbBinlogRepo() repository.DbBinlog {
|
||||
@@ -21,8 +20,18 @@ func NewDbBinlogRepo() repository.DbBinlog {
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ func (repo *dbBinlogHistoryRepoImpl) Upsert(_ context.Context, history *entity.D
|
||||
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:
|
||||
|
||||
@@ -6,74 +6,32 @@ import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"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"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type dbJobBase[T entity.DbJob] struct {
|
||||
var _ repository.DbJobBase = (*dbJobBaseImpl[entity.DbJob])(nil)
|
||||
|
||||
type dbJobBaseImpl[T entity.DbJob] struct {
|
||||
base.RepoImpl[T]
|
||||
}
|
||||
|
||||
func (d *dbJobBase[T]) GetById(e entity.DbJob, id uint64, cols ...string) error {
|
||||
func (d *dbJobBaseImpl[T]) GetById(e entity.DbJob, id uint64, cols ...string) error {
|
||||
return d.RepoImpl.GetById(e.(T), id, cols...)
|
||||
}
|
||||
|
||||
func (d *dbJobBase[T]) UpdateById(ctx context.Context, e entity.DbJob, columns ...string) error {
|
||||
func (d *dbJobBaseImpl[T]) UpdateById(ctx context.Context, e entity.DbJob, columns ...string) error {
|
||||
return d.RepoImpl.UpdateById(ctx, e.(T), columns...)
|
||||
}
|
||||
|
||||
func (d *dbJobBase[T]) UpdateEnabled(_ context.Context, jobId uint64, enabled bool) error {
|
||||
cond := map[string]any{
|
||||
"id": jobId,
|
||||
}
|
||||
return d.Updates(cond, map[string]any{
|
||||
"enabled": enabled,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *dbJobBase[T]) UpdateLastStatus(ctx context.Context, job entity.DbJob) error {
|
||||
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 (d *dbJobBase[T]) ListToDo(jobs any) error {
|
||||
db := global.Db.Model(d.GetModel())
|
||||
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
|
||||
}
|
||||
|
||||
func (d *dbJobBase[T]) ListRepeating(jobs any) error {
|
||||
cond := map[string]any{
|
||||
"enabled": true,
|
||||
"repeated": true,
|
||||
}
|
||||
if err := d.ListByCond(cond, jobs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageList 分页获取数据库备份任务列表
|
||||
func (d *dbJobBase[T]) GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||
d.GetModel()
|
||||
qd := gormx.NewQuery(d.GetModel()).
|
||||
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 gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
func (d *dbJobBase[T]) AddJob(ctx context.Context, jobs any) error {
|
||||
func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any) error {
|
||||
// refactor and jobs from any to []T
|
||||
return gormx.Tx(func(db *gorm.DB) error {
|
||||
var instanceId uint64
|
||||
var dbNames []string
|
||||
@@ -93,26 +51,28 @@ func (d *dbJobBase[T]) AddJob(ctx context.Context, jobs any) error {
|
||||
if jobBase.DbInstanceId != instanceId {
|
||||
return errors.New("不支持同时为多个数据库实例添加数据库任务")
|
||||
}
|
||||
if jobBase.Interval == 0 {
|
||||
if job.GetInterval() == 0 {
|
||||
// 单次执行的数据库任务可重复创建
|
||||
continue
|
||||
}
|
||||
dbNames = append(dbNames, jobBase.DbName)
|
||||
dbNames = append(dbNames, job.GetDbName())
|
||||
}
|
||||
default:
|
||||
jobBase := jobs.(entity.DbJob).GetJobBase()
|
||||
job := jobs.(entity.DbJob)
|
||||
jobBase := job.GetJobBase()
|
||||
instanceId = jobBase.DbInstanceId
|
||||
if jobBase.Interval > 0 {
|
||||
dbNames = append(dbNames, jobBase.DbName)
|
||||
if job.GetInterval() > 0 {
|
||||
dbNames = append(dbNames, job.GetDbName())
|
||||
}
|
||||
}
|
||||
|
||||
var res []string
|
||||
err := db.Model(d.GetModel()).Select("db_name").
|
||||
err := db.Model(repo.GetModel()).Select("db_name").
|
||||
Where("db_instance_id = ?", instanceId).
|
||||
Where("db_name in ?", dbNames).
|
||||
Where("repeated = true").
|
||||
Scopes(gormx.UndeleteScope).Find(&res).Error
|
||||
Scopes(gormx.UndeleteScope).
|
||||
Find(&res).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -120,8 +80,8 @@ func (d *dbJobBase[T]) AddJob(ctx context.Context, jobs any) error {
|
||||
return errors.New(fmt.Sprintf("数据库任务已存在: %v", res))
|
||||
}
|
||||
if plural {
|
||||
return d.BatchInsertWithDb(ctx, db, jobs)
|
||||
return repo.BatchInsertWithDb(ctx, db, jobs.([]T))
|
||||
}
|
||||
return d.InsertWithDb(ctx, db, jobs.(T))
|
||||
return repo.InsertWithDb(ctx, db, jobs.(T))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
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 {
|
||||
dbJobBase[*entity.DbRestore]
|
||||
dbJobBaseImpl[*entity.DbRestore]
|
||||
}
|
||||
|
||||
func NewDbRestoreRepo() repository.DbRestore {
|
||||
@@ -21,7 +24,8 @@ func (d *dbRestoreRepoImpl) GetDbNamesWithoutRestore(instanceId uint64, dbNames
|
||||
var dbNamesWithRestore []string
|
||||
query := gormx.NewQuery(d.GetModel()).
|
||||
Eq("db_instance_id", instanceId).
|
||||
Eq("repeated", true)
|
||||
Eq("repeated", true).
|
||||
Undeleted()
|
||||
if err := query.GenGdb().Pluck("db_name", &dbNamesWithRestore).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,3 +37,41 @@ func (d *dbRestoreRepoImpl) GetDbNamesWithoutRestore(instanceId uint64, dbNames
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *dbRestoreRepoImpl) ListToDo(jobs any) error {
|
||||
db := global.Db.Model(d.GetModel())
|
||||
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.DbJobQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||
d.GetModel()
|
||||
qd := gormx.NewQuery(d.GetModel()).
|
||||
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 gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
// AddJob 添加数据库任务
|
||||
func (d *dbRestoreRepoImpl) AddJob(ctx context.Context, jobs any) error {
|
||||
return addJob[*entity.DbRestore](ctx, d.dbJobBaseImpl, jobs)
|
||||
}
|
||||
|
||||
func (d *dbRestoreRepoImpl) UpdateEnabled(_ context.Context, jobId uint64, enabled bool) error {
|
||||
cond := map[string]any{
|
||||
"id": jobId,
|
||||
}
|
||||
return d.Updates(cond, map[string]any{
|
||||
"enabled": enabled,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ type Team struct {
|
||||
}
|
||||
|
||||
func (p *Team) GetTeams(rc *req.Ctx) {
|
||||
queryCond, page := ginx.BindQueryAndPage(rc.GinCtx, new(entity.TeamQuery))
|
||||
teams := &[]entity.Team{}
|
||||
res, err := p.TeamApp.GetPageList(&entity.Team{}, ginx.GetPageParam(rc.GinCtx), teams)
|
||||
res, err := p.TeamApp.GetPageList(queryCond, page, teams)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
type Team interface {
|
||||
// 分页获取项目团队信息列表
|
||||
GetPageList(condition *entity.Team, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.TeamQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
Save(ctx context.Context, team *entity.Team) error
|
||||
|
||||
@@ -55,7 +55,7 @@ type teamAppImpl struct {
|
||||
tagTreeTeamRepo repository.TagTreeTeam
|
||||
}
|
||||
|
||||
func (p *teamAppImpl) GetPageList(condition *entity.Team, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (p *teamAppImpl) GetPageList(condition *entity.TeamQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return p.teamRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,3 +26,9 @@ type TagResourceQuery struct {
|
||||
TagPathLike string // 标签路径模糊查询
|
||||
TagPathLikes []string
|
||||
}
|
||||
|
||||
type TeamQuery struct {
|
||||
model.Model
|
||||
|
||||
Name string `json:"name" form:"name"` // 团队名称
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ import (
|
||||
type Team interface {
|
||||
base.Repo[*entity.Team]
|
||||
|
||||
GetPageList(condition *entity.Team, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.TeamQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ type teamRepoImpl struct {
|
||||
}
|
||||
|
||||
func newTeamRepo() repository.Team {
|
||||
return &teamRepoImpl{base.RepoImpl[*entity.Team]{M: new(entity.Team)}}
|
||||
return &teamRepoImpl{}
|
||||
}
|
||||
|
||||
func (p *teamRepoImpl) GetPageList(condition *entity.Team, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := gormx.NewQuery(condition).WithCondModel(condition).WithOrderBy(orderBy...)
|
||||
func (p *teamRepoImpl) GetPageList(condition *entity.TeamQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := gormx.NewQuery(p.GetModel()).
|
||||
Like("name", condition.Name).
|
||||
WithOrderBy()
|
||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user