Files
mayfly-go/server/internal/db/application/db.go
kanzihuang ae3d2659aa 重构数据库备份与恢复模块 (#80)
* 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 文件,忽略数据库备份目录和数据库程序目录
2024-01-05 08:55:34 +08:00

204 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package application
import (
"context"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/utils/structx"
"strings"
)
type Db interface {
base.App[*entity.Db]
// 分页获取
GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
Count(condition *entity.DbQuery) int64
Save(ctx context.Context, entity *entity.Db, tagIds ...uint64) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
// 获取数据库连接实例
// @param id 数据库id
//
// @param dbName 数据库名
GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error)
// 根据数据库实例id获取连接随机返回该instanceId下已连接的conn若不存在则是使用该instanceId关联的db进行连接并返回。
GetDbConnByInstanceId(instanceId uint64) (*dbm.DbConn, error)
}
func newDbApp(dbRepo repository.Db, dbSqlRepo repository.DbSql, dbInstanceApp Instance, tagApp tagapp.TagTree) Db {
app := &dbAppImpl{
dbSqlRepo: dbSqlRepo,
dbInstanceApp: dbInstanceApp,
tagApp: tagApp,
}
app.Repo = dbRepo
return app
}
type dbAppImpl struct {
base.AppImpl[*entity.Db, repository.Db]
dbSqlRepo repository.DbSql
dbInstanceApp Instance
tagApp tagapp.TagTree
}
// 分页获取数据库信息列表
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
return d.GetRepo().GetDbList(condition, pageParam, toEntity, orderBy...)
}
func (d *dbAppImpl) Count(condition *entity.DbQuery) int64 {
return d.GetRepo().Count(condition)
}
func (d *dbAppImpl) Save(ctx context.Context, dbEntity *entity.Db, tagIds ...uint64) error {
// 查找是否存在
oldDb := &entity.Db{Name: dbEntity.Name, InstanceId: dbEntity.InstanceId}
err := d.GetBy(oldDb)
if dbEntity.Id == 0 {
if err == nil {
return errorx.NewBiz("该实例下数据库名已存在")
}
resouceCode := stringx.Rand(16)
dbEntity.Code = resouceCode
return d.Tx(ctx, func(ctx context.Context) error {
return d.Insert(ctx, dbEntity)
}, func(ctx context.Context) error {
return d.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeDb, tagIds)
})
}
// 如果存在该库,则校验修改的库是否为该库
if err == nil && oldDb.Id != dbEntity.Id {
return errorx.NewBiz("该实例下数据库名已存在")
}
dbId := dbEntity.Id
old, err := d.GetById(new(entity.Db), dbId)
if err != nil {
return errorx.NewBiz("该数据库不存在")
}
oldDbs := strings.Split(old.Database, " ")
newDbs := strings.Split(dbEntity.Database, " ")
// 比较新旧数据库列表,需要将移除的数据库相关联的信息删除
_, delDb, _ := collx.ArrayCompare(newDbs, oldDbs, func(i1, i2 string) bool {
return i1 == i2
})
for _, v := range delDb {
// 关闭数据库连接
dbm.CloseDb(dbEntity.Id, v)
// 删除该库关联的所有sql记录
d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: dbId, Db: v})
}
return d.Tx(ctx, func(ctx context.Context) error {
return d.UpdateById(ctx, dbEntity)
}, func(ctx context.Context) error {
return d.tagApp.RelateResource(ctx, oldDb.Code, consts.TagResourceTypeDb, tagIds)
})
}
func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
db, err := d.GetById(new(entity.Db), id)
if err != nil {
return errorx.NewBiz("该数据库不存在")
}
dbs := strings.Split(db.Database, " ")
for _, v := range dbs {
// 关闭连接
dbm.CloseDb(id, v)
}
return d.Tx(ctx,
func(ctx context.Context) error {
return d.DeleteById(ctx, id)
},
func(ctx context.Context) error {
// 删除该库下用户保存的所有sql信息
return d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: id})
}, func(ctx context.Context) error {
var tagIds []uint64
return d.tagApp.RelateResource(ctx, db.Code, consts.TagResourceTypeDb, tagIds)
})
}
func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error) {
return dbm.GetDbConn(dbId, dbName, func() (*dbm.DbInfo, error) {
db, err := d.GetById(new(entity.Db), dbId)
if err != nil {
return nil, errorx.NewBiz("数据库信息不存在")
}
instance, err := d.dbInstanceApp.GetById(new(entity.DbInstance), db.InstanceId)
if err != nil {
return nil, errorx.NewBiz("数据库实例不存在")
}
checkDb := dbName
// 兼容pgsql/dm db/schema模式
if dbm.DbTypePostgres.Equal(instance.Type) || dbm.DM.Equal(instance.Type) {
ss := strings.Split(dbName, "/")
if len(ss) > 1 {
checkDb = ss[0]
}
}
if !strings.Contains(" "+db.Database+" ", " "+checkDb+" ") {
return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName)
}
// 密码解密
if err := instance.PwdDecrypt(); err != nil {
return nil, errorx.NewBiz(err.Error())
}
return toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...), nil
})
}
func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbm.DbConn, error) {
conn := dbm.GetDbConnByInstanceId(instanceId)
if conn != nil {
return conn, nil
}
var dbs []*entity.Db
err := d.ListByCond(&entity.Db{InstanceId: instanceId}, &dbs, "id", "database")
if err != nil || len(dbs) == 0 {
return nil, errorx.NewBiz("该实例未配置数据库, 请先进行配置")
}
// 使用该实例关联的已配置数据库中的第一个库进行连接并返回
firstDb := dbs[0]
return d.GetDbConn(firstDb.Id, strings.Split(firstDb.Database, " ")[0])
}
func toDbInfo(instance *entity.DbInstance, dbId uint64, database string, tagPath ...string) *dbm.DbInfo {
di := new(dbm.DbInfo)
di.InstanceId = instance.Id
di.Id = dbId
di.Database = database
di.TagPath = tagPath
structx.Copy(di, instance)
return di
}