!84 fix: 修复数据库备份与恢复问题

* refactor dbScheduler
* fix: 按团队名称检索团队
* feat: 创建数据库资源时支持全选数据库
* refactor dbScheduler
* fix: 修复数据库备份与恢复问题
This commit is contained in:
kanzihuang
2024-01-17 08:37:22 +00:00
committed by Coder慌
parent cc3981d99c
commit 94da6df33e
35 changed files with 846 additions and 609 deletions

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -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))
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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,
})
}

View File

@@ -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")
}

View File

@@ -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:

View File

@@ -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))
})
}

View File

@@ -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,
})
}

View File

@@ -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
}

View File

@@ -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...)
}

View File

@@ -26,3 +26,9 @@ type TagResourceQuery struct {
TagPathLike string // 标签路径模糊查询
TagPathLikes []string
}
type TeamQuery struct {
model.Model
Name string `json:"name" form:"name"` // 团队名称
}

View File

@@ -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)
}

View File

@@ -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)
}