mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			284 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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)
 | 
						|
}
 |