mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 16:30:25 +08:00 
			
		
		
		
	* fix: 保存 LastResult 时截断字符串过长部分,以避免数据库报错 * refactor: 新增 entity.DbTaskBase 和 persistence.dbTaskBase, 用于实现数据库备份和恢复任务处理相关部分 * fix: aeskey变更后,解密密码出现数组越界访问错误 * fix: 时间属性为零值时,保存到 mysql 数据库报错 * refactor db.infrastructure.service.scheduler * feat: 实现立即备份功能 * refactor db.infrastructure.service.db_instance * refactor: 从数据库中获取数据库备份目录、mysql文件路径等配置信息 * fix: 数据库备份和恢复问题 * fix: 修改 .gitignore 文件,忽略数据库备份目录和数据库程序目录
		
			
				
	
	
		
			277 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			277 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
//go:build e2e
 | 
						|
 | 
						|
package dbm
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"github.com/stretchr/testify/suite"
 | 
						|
	"mayfly-go/internal/db/config"
 | 
						|
	"mayfly-go/internal/db/domain/entity"
 | 
						|
	"mayfly-go/internal/db/domain/repository"
 | 
						|
	"mayfly-go/internal/db/infrastructure/persistence"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	instanceIdTest          = 0
 | 
						|
	backupIdTest            = 0
 | 
						|
	dbNameBackupTest        = "test-backup-01"
 | 
						|
	tableNameBackupTest     = "test-backup"
 | 
						|
	tableNameRestorePITTest = "test-restore-pit"
 | 
						|
	tableNameNoBackupTest   = "test-not-backup"
 | 
						|
)
 | 
						|
 | 
						|
type DbInstanceSuite struct {
 | 
						|
	suite.Suite
 | 
						|
	repositories *repository.Repositories
 | 
						|
	instanceSvc  *DbProgramMysql
 | 
						|
	dbConn       *DbConn
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) SetupSuite() {
 | 
						|
	if err := chdir("mayfly-go", "server"); err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	dbInfo := DbInfo{
 | 
						|
		Type:     DbTypeMysql,
 | 
						|
		Host:     "localhost",
 | 
						|
		Port:     3306,
 | 
						|
		Username: "test",
 | 
						|
		Password: "test",
 | 
						|
	}
 | 
						|
	dbConn, err := dbInfo.Conn()
 | 
						|
	s.Require().NoError(err)
 | 
						|
	s.dbConn = dbConn
 | 
						|
	s.repositories = &repository.Repositories{
 | 
						|
		Instance:       persistence.GetInstanceRepo(),
 | 
						|
		Backup:         persistence.NewDbBackupRepo(),
 | 
						|
		BackupHistory:  persistence.NewDbBackupHistoryRepo(),
 | 
						|
		Restore:        persistence.NewDbRestoreRepo(),
 | 
						|
		RestoreHistory: persistence.NewDbRestoreHistoryRepo(),
 | 
						|
		Binlog:         persistence.NewDbBinlogRepo(),
 | 
						|
		BinlogHistory:  persistence.NewDbBinlogHistoryRepo(),
 | 
						|
	}
 | 
						|
	s.instanceSvc = NewDbProgramMysql(s.dbConn)
 | 
						|
	var extName string
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		extName = ".exe"
 | 
						|
	}
 | 
						|
	path := "db/mysql/bin"
 | 
						|
	s.instanceSvc.mysqlBin = &config.MysqlBin{
 | 
						|
		Path:            filepath.Join(path),
 | 
						|
		MysqlPath:       filepath.Join(path, "mysql"+extName),
 | 
						|
		MysqldumpPath:   filepath.Join(path, "mysqldump"+extName),
 | 
						|
		MysqlbinlogPath: filepath.Join(path, "mysqlbinlog"+extName),
 | 
						|
	}
 | 
						|
	s.instanceSvc.backupPath = "db/backup"
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) TearDownSuite() {
 | 
						|
	if s.dbConn != nil {
 | 
						|
		s.dbConn.Close()
 | 
						|
		s.dbConn = nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) SetupTest() {
 | 
						|
	sql := strings.Builder{}
 | 
						|
	require := s.Require()
 | 
						|
	sql.WriteString(fmt.Sprintf("drop database if exists `%s`;", dbNameBackupTest))
 | 
						|
	sql.WriteString(fmt.Sprintf("create database `%s`;", dbNameBackupTest))
 | 
						|
	require.NoError(s.instanceSvc.execute("", sql.String()))
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) TearDownTest() {
 | 
						|
	require := s.Require()
 | 
						|
	sql := fmt.Sprintf("drop database if exists `%s`", dbNameBackupTest)
 | 
						|
	require.NoError(s.instanceSvc.execute("", sql))
 | 
						|
 | 
						|
	_ = os.RemoveAll(s.instanceSvc.getDbInstanceBackupRoot(instanceIdTest))
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) TestBackup() {
 | 
						|
	task := &entity.DbBackupHistory{
 | 
						|
		DbName: dbNameBackupTest,
 | 
						|
		Uuid:   dbNameBackupTest,
 | 
						|
	}
 | 
						|
	task.Id = backupIdTest
 | 
						|
	s.testBackup(task)
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) testBackup(backupHistory *entity.DbBackupHistory) {
 | 
						|
	require := s.Require()
 | 
						|
	binlogInfo, err := s.instanceSvc.Backup(context.Background(), backupHistory)
 | 
						|
	require.NoError(err)
 | 
						|
 | 
						|
	fileName := filepath.Join(s.instanceSvc.getDbBackupDir(s.dbConn.Info.InstanceId, backupHistory.Id), dbNameBackupTest+".sql")
 | 
						|
	_, err = os.Stat(fileName)
 | 
						|
	require.NoError(err)
 | 
						|
 | 
						|
	backupHistory.BinlogFileName = binlogInfo.FileName
 | 
						|
	backupHistory.BinlogSequence = binlogInfo.Sequence
 | 
						|
	backupHistory.BinlogPosition = binlogInfo.Position
 | 
						|
}
 | 
						|
 | 
						|
func TestDbInstance(t *testing.T) {
 | 
						|
	suite.Run(t, &DbInstanceSuite{})
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) TestRestoreDatabase() {
 | 
						|
	backupHistory := &entity.DbBackupHistory{
 | 
						|
		DbName: dbNameBackupTest,
 | 
						|
		Uuid:   dbNameBackupTest,
 | 
						|
	}
 | 
						|
 | 
						|
	s.createTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.testBackup(backupHistory)
 | 
						|
	s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
						|
	s.testRestore(backupHistory)
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) TestRestorePontInTime() {
 | 
						|
	backupHistory := &entity.DbBackupHistory{
 | 
						|
		DbName: dbNameBackupTest,
 | 
						|
		Uuid:   dbNameBackupTest,
 | 
						|
	}
 | 
						|
 | 
						|
	s.createTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.testBackup(backupHistory)
 | 
						|
 | 
						|
	s.createTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
						|
	time.Sleep(time.Second)
 | 
						|
	targetTime := time.Now()
 | 
						|
 | 
						|
	s.dropTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "运行 mysql 程序失败")
 | 
						|
	s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
						|
 | 
						|
	s.testRestore(backupHistory)
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "运行 mysql 程序失败")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
						|
 | 
						|
	s.testReplayBinlog(backupHistory, targetTime)
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
						|
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) testReplayBinlog(backupHistory *entity.DbBackupHistory, targetTime time.Time) {
 | 
						|
	require := s.Require()
 | 
						|
	binlogFilesOnServerSorted, err := s.instanceSvc.GetSortedBinlogFilesOnServer(context.Background())
 | 
						|
	require.NoError(err)
 | 
						|
	require.True(len(binlogFilesOnServerSorted) > 0, "binlog 文件不存在")
 | 
						|
	for i, bf := range binlogFilesOnServerSorted {
 | 
						|
		if bf.Name == backupHistory.BinlogFileName {
 | 
						|
			binlogFilesOnServerSorted = binlogFilesOnServerSorted[i:]
 | 
						|
			break
 | 
						|
		}
 | 
						|
		require.Less(i, len(binlogFilesOnServerSorted), "binlog 文件没找到")
 | 
						|
	}
 | 
						|
	err = s.instanceSvc.downloadBinlogFilesOnServer(context.Background(), binlogFilesOnServerSorted, true)
 | 
						|
	require.NoError(err)
 | 
						|
 | 
						|
	binlogFileLast := binlogFilesOnServerSorted[len(binlogFilesOnServerSorted)-1]
 | 
						|
	position, err := s.instanceSvc.GetBinlogEventPositionAtOrAfterTime(context.Background(), binlogFileLast.Name, targetTime)
 | 
						|
	require.NoError(err)
 | 
						|
	binlogHistories := make([]*entity.DbBinlogHistory, 0, 2)
 | 
						|
	binlogHistoryBackup := &entity.DbBinlogHistory{
 | 
						|
		FileName: backupHistory.BinlogFileName,
 | 
						|
		Sequence: backupHistory.BinlogSequence,
 | 
						|
	}
 | 
						|
	binlogHistories = append(binlogHistories, binlogHistoryBackup)
 | 
						|
	if binlogHistoryBackup.Sequence != binlogFileLast.Sequence {
 | 
						|
		require.Equal(binlogFilesOnServerSorted[0].Sequence, binlogHistoryBackup.Sequence)
 | 
						|
		binlogHistoryLast := &entity.DbBinlogHistory{
 | 
						|
			FileName: binlogFileLast.Name,
 | 
						|
			Sequence: binlogFileLast.Sequence,
 | 
						|
		}
 | 
						|
		binlogHistories = append(binlogHistories, binlogHistoryLast)
 | 
						|
	}
 | 
						|
 | 
						|
	restoreInfo := &RestoreInfo{
 | 
						|
		BackupHistory:   backupHistory,
 | 
						|
		BinlogHistories: binlogHistories,
 | 
						|
		StartPosition:   backupHistory.BinlogPosition,
 | 
						|
		TargetPosition:  position,
 | 
						|
		TargetTime:      targetTime,
 | 
						|
	}
 | 
						|
	err = s.instanceSvc.ReplayBinlog(context.Background(), dbNameBackupTest, dbNameBackupTest, restoreInfo)
 | 
						|
	require.NoError(err)
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) testRestore(backupHistory *entity.DbBackupHistory) {
 | 
						|
	require := s.Require()
 | 
						|
	err := s.instanceSvc.RestoreBackupHistory(context.Background(), backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid)
 | 
						|
	require.NoError(err)
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) selectTable(database, tableName, wantErr string) {
 | 
						|
	require := s.Require()
 | 
						|
	sql := fmt.Sprintf("select * from`%s`;", tableName)
 | 
						|
	err := s.instanceSvc.execute(database, sql)
 | 
						|
	if len(wantErr) > 0 {
 | 
						|
		require.ErrorContains(err, wantErr)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	require.NoError(err)
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) createTable(database, tableName, wantErr string) {
 | 
						|
	require := s.Require()
 | 
						|
	sql := fmt.Sprintf("create table `%s`(id int);", tableName)
 | 
						|
	err := s.instanceSvc.execute(database, sql)
 | 
						|
	if len(wantErr) > 0 {
 | 
						|
		require.ErrorContains(err, wantErr)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	require.NoError(err)
 | 
						|
}
 | 
						|
 | 
						|
func (s *DbInstanceSuite) dropTable(database, tableName, wantErr string) {
 | 
						|
	require := s.Require()
 | 
						|
	sql := fmt.Sprintf("drop table `%s`;", tableName)
 | 
						|
	err := s.instanceSvc.execute(database, sql)
 | 
						|
	if len(wantErr) > 0 {
 | 
						|
		require.ErrorContains(err, wantErr)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	require.NoError(err)
 | 
						|
}
 | 
						|
 | 
						|
func chdir(projectName string, subdir ...string) error {
 | 
						|
	subdir = append([]string{"/", projectName}, subdir...)
 | 
						|
	suffix := filepath.Join(subdir...)
 | 
						|
	wd, err := os.Getwd()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	for {
 | 
						|
		if strings.HasSuffix(wd, suffix) {
 | 
						|
			if err := os.Chdir(wd); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		upper := filepath.Join(wd, "..")
 | 
						|
		if upper == wd {
 | 
						|
			return errors.New(fmt.Sprintf("not found directory: %s", suffix[1:]))
 | 
						|
		}
 | 
						|
		wd = upper
 | 
						|
	}
 | 
						|
}
 |