refactor: 数据库实例与凭证关联至标签&其他问题修复重构等

This commit is contained in:
meilin.huang
2024-04-17 21:28:28 +08:00
parent f4162c38db
commit 01d3e1ad28
68 changed files with 1421 additions and 809 deletions

View File

@@ -1,9 +1,9 @@
package api
import (
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/application"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
@@ -15,9 +15,10 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
dbNum := len(m.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeDb, ""))
tagCodePaths := m.TagTreeApp.GetAccountTagCodePaths(accountId, tagentity.TagTypeDbName, "")
rc.ResData = collx.M{
"dbNum": dbNum,
"dbNum": len(tagCodePaths),
}
}

View File

@@ -44,7 +44,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
// 不存在可访问标签id即没有可操作数据
codes := d.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeDb, queryCond.TagPath)
codes := d.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, int8(tagentity.TagTypeDbName), queryCond.TagPath)
if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
@@ -56,7 +56,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
biz.ErrIsNil(err)
// 填充标签信息
d.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDb), collx.ArrayMap(dbvos, func(dbvo *vo.DbListVO) tagentity.ITagResource {
d.TagApp.FillTagInfo(tagentity.TagTypeDbName, collx.ArrayMap(dbvos, func(dbvo *vo.DbListVO) tagentity.ITagResource {
return dbvo
})...)
@@ -69,7 +69,7 @@ func (d *Db) Save(rc *req.Ctx) {
rc.ReqParam = form
biz.ErrIsNil(d.DbApp.SaveDb(rc.MetaCtx, db, form.TagId...))
biz.ErrIsNil(d.DbApp.SaveDb(rc.MetaCtx, db))
}
func (d *Db) DeleteDb(rc *req.Ctx) {
@@ -81,7 +81,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
for _, v := range ids {
dbId := cast.ToUint64(v)
biz.NotBlank(dbId, "存在错误dbId")
d.DbApp.Delete(ctx, dbId)
biz.ErrIsNil(d.DbApp.Delete(ctx, dbId))
// 删除该库的sql执行记录
d.DbSqlExecApp.DeleteBy(ctx, &entity.DbSqlExec{DbId: dbId})
}

View File

@@ -10,6 +10,7 @@ import (
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"strconv"
@@ -20,6 +21,7 @@ type Instance struct {
InstanceApp application.Instance `inject:"DbInstanceApp"`
DbApp application.Db `inject:""`
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
TagApp tagapp.TagTree `inject:"TagTreeApp"`
}
// Instances 获取数据库实例信息
@@ -27,15 +29,30 @@ type Instance struct {
func (d *Instance) Instances(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery))
tagCodePaths := d.TagApp.GetAccountTagCodePaths(rc.GetLoginAccount().Id, tagentity.TagTypeDbAuthCert, queryCond.TagPath)
// 不存在可操作的数据库,即没有可操作数据
if len(tagCodePaths) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
dbInstCodes := tagentity.GetCodeByPath(tagentity.TagTypeDb, tagCodePaths...)
queryCond.Codes = dbInstCodes
var instvos []*vo.InstanceListVO
res, err := d.InstanceApp.GetPageList(queryCond, page, &instvos)
biz.ErrIsNil(err)
// 填充授权凭证信息
d.ResourceAuthCertApp.FillAuthCert(consts.ResourceTypeDb, collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
d.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodeByPath(tagentity.TagTypeDbAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
return vos
})...)
// 填充标签信息
d.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDb), collx.ArrayMap(instvos, func(insvo *vo.InstanceListVO) tagentity.ITagResource {
return insvo
})...)
rc.ResData = res
}
@@ -53,10 +70,13 @@ func (d *Instance) SaveInstance(rc *req.Ctx) {
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
rc.ReqParam = form
biz.ErrIsNil(d.InstanceApp.SaveDbInstance(rc.MetaCtx, &application.SaveDbInstanceParam{
DbInstance: instance,
AuthCerts: form.AuthCerts,
}))
id, err := d.InstanceApp.SaveDbInstance(rc.MetaCtx, &application.SaveDbInstanceParam{
DbInstance: instance,
AuthCerts: form.AuthCerts,
TagCodePaths: form.TagCodePaths,
})
biz.ErrIsNil(err)
rc.ResData = id
}
// GetInstance 获取数据库实例密码,由于数据库是加密存储,故提供该接口展示原文密码
@@ -86,14 +106,9 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) {
// 获取数据库实例的所有数据库名
func (d *Instance) GetDatabaseNames(rc *req.Ctx) {
instanceId := getInstanceId(rc)
authCertName := rc.Query("authCertName")
biz.NotEmpty(authCertName, "授权凭证名不能为空")
instance, err := d.InstanceApp.GetById(new(entity.DbInstance), instanceId)
biz.ErrIsNil(err, "获取数据库实例错误")
res, err := d.InstanceApp.GetDatabases(instance, authCertName)
form := &form.InstanceDbNamesForm{}
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
res, err := d.InstanceApp.GetDatabases(instance, form.AuthCert)
biz.ErrIsNil(err)
rc.ResData = res
}

View File

@@ -1,15 +1,14 @@
package form
type DbForm struct {
Id uint64 `json:"id"`
Code string `binding:"required" json:"code"`
Name string `binding:"required" json:"name"`
Database string `json:"database"`
Remark string `json:"remark"`
TagId []uint64 `binding:"required" json:"tagId"`
InstanceId uint64 `binding:"required" json:"instanceId"`
AuthCertName string `json: "authCertName"`
FlowProcdefKey string `json:"flowProcdefKey"`
Id uint64 `json:"id"`
Code string `binding:"required" json:"code"`
Name string `binding:"required" json:"name"`
Database string `json:"database"`
Remark string `json:"remark"`
InstanceId uint64 `binding:"required" json:"instanceId"`
AuthCertName string `json:"authCertName"`
FlowProcdefKey string `json:"flowProcdefKey"`
}
type DbSqlSaveForm struct {

View File

@@ -14,5 +14,15 @@ type InstanceForm struct {
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"`
AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts" binding:"required"` // 资产授权凭证信息列表
AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts" binding:"required"` // 资产授权凭证信息列表
TagCodePaths []string `binding:"required" json:"tagCodePaths"`
}
type InstanceDbNamesForm struct {
Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"`
Port int `json:"port"`
Params string `json:"params"`
SshTunnelMachineId int `json:"sshTunnelMachineId"`
AuthCert *tagentity.ResourceAuthCert `json:"authCert" binding:"required"` // 资产授权凭证信息
}

View File

@@ -7,6 +7,7 @@ import (
type InstanceListVO struct {
tagentity.AuthCerts // 授权凭证信息
tagentity.ResourceTags
Id *int64 `json:"id"`
Code string `json:"code"`

View File

@@ -3,7 +3,6 @@ package application
import (
"context"
"fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
@@ -28,7 +27,7 @@ type Db interface {
Count(condition *entity.DbQuery) int64
SaveDb(ctx context.Context, entity *entity.Db, tagIds ...uint64) error
SaveDb(ctx context.Context, entity *entity.Db) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -49,9 +48,10 @@ type Db interface {
type dbAppImpl struct {
base.AppImpl[*entity.Db, repository.Db]
dbSqlRepo repository.DbSql `inject:"DbSqlRepo"`
dbInstanceApp Instance `inject:"DbInstanceApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
dbSqlRepo repository.DbSql `inject:"DbSqlRepo"`
dbInstanceApp Instance `inject:"DbInstanceApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
}
// 注入DbRepo
@@ -68,11 +68,16 @@ func (d *dbAppImpl) Count(condition *entity.DbQuery) int64 {
return d.GetRepo().Count(condition)
}
func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...uint64) error {
func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
// 查找是否存在
oldDb := &entity.Db{Name: dbEntity.Name, InstanceId: dbEntity.InstanceId}
err := d.GetBy(oldDb)
authCert, err := d.resourceAuthCertApp.GetAuthCert(dbEntity.AuthCertName)
if err != nil {
return errorx.NewBiz("授权凭证不存在")
}
err = d.GetBy(oldDb)
if dbEntity.Id == 0 {
if err == nil {
return errorx.NewBiz("该实例下数据库名已存在")
@@ -85,10 +90,15 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
return d.Tx(ctx, func(ctx context.Context) error {
return d.Insert(ctx, dbEntity)
}, func(ctx context.Context) error {
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: dbEntity.Code,
Type: tagentity.TagTypeDb,
ParentTagIds: tagIds,
// 将库关联至指定数据库授权凭证下
return d.tagApp.RelateTagsByCodeAndType(ctx, &tagapp.RelateTagsByCodeAndTypeParam{
Tags: []*tagapp.ResourceTag{{
Code: dbEntity.Code,
Type: tagentity.TagTypeDbName,
Name: dbEntity.Name,
}},
ParentTagCode: authCert.Name,
ParentTagType: tagentity.TagTypeDbAuthCert,
})
})
}
@@ -125,11 +135,15 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
return d.Tx(ctx, func(ctx context.Context) error {
return d.UpdateById(ctx, dbEntity)
}, func(ctx context.Context) error {
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: old.Code,
Type: tagentity.TagTypeDb,
ParentTagIds: tagIds,
})
if old.Name != dbEntity.Name {
if err := d.tagApp.UpdateTagName(ctx, tagentity.TagTypeDbName, old.Code, dbEntity.Name); err != nil {
return err
}
}
if authCert.Name != old.AuthCertName {
return d.tagApp.UpdateParentTagCode(ctx, tagentity.TagTypeDbName, old.Code, authCert.Name, authCert.Username)
}
return nil
})
}
@@ -152,9 +166,9 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
// 删除该库下用户保存的所有sql信息
return d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: id})
}, func(ctx context.Context) error {
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: db.Code,
Type: tagentity.TagTypeDb,
return d.tagApp.DeleteTagByParam(ctx, &tagapp.DelResourceTagParam{
ResourceCode: db.Code,
ResourceType: tagentity.TagTypeDbName,
})
})
}
@@ -175,7 +189,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
if err != nil {
return nil, err
}
di.TagPath = d.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeDb, db.Code)
di.TagPath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDbName), db.Code)
di.Id = db.Id
if db.FlowProcdefKey != nil {
@@ -264,9 +278,11 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *DumpDbReq) error {
writer.TryFlush()
// 查询表信息,主要是为了查询表注释
tbs, err := dbMeta.GetTables(tableName)
biz.ErrIsNil(err)
if err != nil || tbs == nil || len(tbs) <= 0 {
panic(errorx.NewBiz(fmt.Sprintf("获取表信息失败:%s", tableName)))
if err != nil {
return err
}
if len(tbs) <= 0 {
return errorx.NewBiz(fmt.Sprintf("获取表信息失败:%s", tableName))
}
tabInfo := dbi.Table{
TableName: tableName,
@@ -293,7 +309,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *DumpDbReq) error {
quoteColNames = append(quoteColNames, dbMeta.QuoteIdentifier(col.ColumnName))
}
_ = dbConn.WalkTableRows(ctx, quoteTableName, func(row map[string]any, _ []*dbi.QueryColumn) error {
_, _ = dbConn.WalkTableRows(ctx, quoteTableName, func(row map[string]any, _ []*dbi.QueryColumn) error {
rowValues := make([]string, len(columnMap[tableName]))
for i, col := range columnMap[tableName] {
rowValues[i] = dataHelper.WrapValue(row[col.ColumnName], dataHelper.GetDataType(string(col.DataType)))
@@ -309,7 +325,9 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *DumpDbReq) error {
}
indexs, err := dbMeta.GetTableIndex(tableName)
biz.ErrIsNil(err)
if err != nil {
return err
}
if len(indexs) > 0 {
// 最后添加索引

View File

@@ -243,7 +243,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
updFieldName = strings.Split(task.UpdField, ".")[1]
}
err = srcConn.WalkQueryRows(context.Background(), sql, func(row map[string]any, columns []*dbi.QueryColumn) error {
_, err = srcConn.WalkQueryRows(context.Background(), sql, func(row map[string]any, columns []*dbi.QueryColumn) error {
if len(queryColumns) == 0 {
queryColumns = columns

View File

@@ -13,15 +13,18 @@ import (
"mayfly-go/pkg/base"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/structx"
"gorm.io/gorm"
)
type SaveDbInstanceParam struct {
DbInstance *entity.DbInstance
AuthCerts []*tagentity.ResourceAuthCert
DbInstance *entity.DbInstance
AuthCerts []*tagentity.ResourceAuthCert
TagCodePaths []string
}
type Instance interface {
@@ -34,13 +37,13 @@ type Instance interface {
TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error
SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) error
SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) (uint64, error)
// Delete 删除数据库信息
Delete(ctx context.Context, id uint64) error
// GetDatabases 获取数据库实例的所有数据库列表
GetDatabases(entity *entity.DbInstance, authCertName string) ([]string, error)
GetDatabases(entity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) ([]string, error)
// ToDbInfo 根据实例与授权凭证返回对应的DbInfo
ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error)
@@ -49,6 +52,7 @@ type Instance interface {
type instanceAppImpl struct {
base.AppImpl[*entity.DbInstance, repository.Instance]
tagApp tagapp.TagTree `inject:"TagTreeApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
dbApp Db `inject:"DbApp"`
backupApp *DbBackupApp `inject:"DbBackupApp"`
@@ -93,15 +97,16 @@ func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance, authCert
return nil
}
func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) error {
func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) (uint64, error) {
instanceEntity := instance.DbInstance
// 默认tcp连接
instanceEntity.Network = instanceEntity.GetNetwork()
resourceType := consts.ResourceTypeDb
authCerts := instance.AuthCerts
tagCodePaths := instance.TagCodePaths
if len(authCerts) == 0 {
return errorx.NewBiz("授权凭证信息不能为空")
return 0, errorx.NewBiz("授权凭证信息不能为空")
}
// 查找是否存在该库
@@ -114,13 +119,13 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
err := app.GetBy(oldInstance)
if instanceEntity.Id == 0 {
if err == nil {
return errorx.NewBiz("该数据库实例已存在")
return 0, errorx.NewBiz("该数据库实例已存在")
}
if app.CountByCond(&entity.DbInstance{Code: instanceEntity.Code}) > 0 {
return errorx.NewBiz("该编码已存在")
return 0, errorx.NewBiz("该编码已存在")
}
return app.Tx(ctx, func(ctx context.Context) error {
return instanceEntity.Id, app.Tx(ctx, func(ctx context.Context) error {
return app.Insert(ctx, instanceEntity)
}, func(ctx context.Context) error {
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
@@ -128,15 +133,19 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
ResourceType: tagentity.TagType(resourceType),
AuthCerts: authCerts,
})
}, func(ctx context.Context) error {
return app.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceTag: app.genDbInstanceResourceTag(instanceEntity, authCerts),
ParentTagCodePaths: tagCodePaths,
})
})
}
// 如果存在该库,则校验修改的库是否为该库
if err == nil && oldInstance.Id != instanceEntity.Id {
return errorx.NewBiz("该数据库实例已存在")
return 0, errorx.NewBiz("该数据库实例已存在")
}
return app.Tx(ctx, func(ctx context.Context) error {
return oldInstance.Id, app.Tx(ctx, func(ctx context.Context) error {
return app.UpdateById(ctx, instanceEntity)
}, func(ctx context.Context) error {
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
@@ -144,6 +153,16 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDb
ResourceType: tagentity.TagType(resourceType),
AuthCerts: authCerts,
})
}, func(ctx context.Context) error {
if instanceEntity.Name != oldInstance.Name {
if err := app.tagApp.UpdateTagName(ctx, tagentity.TagTypeDb, oldInstance.Code, instanceEntity.Name); err != nil {
return err
}
}
return app.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceTag: app.genDbInstanceResourceTag(instanceEntity, authCerts),
ParentTagCodePaths: tagCodePaths,
})
})
}
@@ -200,16 +219,31 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
})
}, func(ctx context.Context) error {
return app.tagApp.DeleteTagByParam(ctx, &tagapp.DelResourceTagParam{
ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
})
})
}
func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance, authCertName string) ([]string, error) {
ed.Network = ed.GetNetwork()
dbi, err := app.ToDbInfo(ed, authCertName, "")
if err != nil {
return nil, err
func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance, authCert *tagentity.ResourceAuthCert) ([]string, error) {
if authCert.Id != 0 {
// 密文可能被清除,故需要重新获取
authCert, _ = app.resourceAuthCertApp.GetAuthCert(authCert.Name)
} else {
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
publicAuthCert, err := app.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
if err != nil {
return nil, err
}
authCert = publicAuthCert
}
}
ed.Network = ed.GetNetwork()
dbi := app.toDbInfoByAc(ed, authCert, "")
dbConn, err := dbm.Conn(dbi)
if err != nil {
return nil, err
@@ -238,3 +272,41 @@ func (app *instanceAppImpl) toDbInfoByAc(instance *entity.DbInstance, ac *tagent
di.Password = ac.Ciphertext
return di
}
func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCerts []*tagentity.ResourceAuthCert) *tagapp.ResourceTag {
authCertTags := collx.ArrayMap[*tagentity.ResourceAuthCert, *tagapp.ResourceTag](authCerts, func(val *tagentity.ResourceAuthCert) *tagapp.ResourceTag {
return &tagapp.ResourceTag{
Code: val.Name,
Name: val.Username,
Type: tagentity.TagTypeDbAuthCert,
}
})
var dbs []*entity.Db
if err := m.dbApp.ListByCond(&entity.Db{
InstanceId: me.Id,
}, &dbs); err != nil {
logx.Errorf("获取实例关联的数据库失败: %v", err)
}
authCertName2DbTags := make(map[string][]*tagapp.ResourceTag)
for _, db := range dbs {
authCertName2DbTags[db.AuthCertName] = append(authCertName2DbTags[db.AuthCertName], &tagapp.ResourceTag{
Code: db.Code,
Name: db.Name,
Type: tagentity.TagTypeDbName,
})
}
// 将数据库挂至授权凭证下
for _, ac := range authCertTags {
ac.Children = authCertName2DbTags[ac.Code]
}
return &tagapp.ResourceTag{
Code: me.Code,
Type: tagentity.TagTypeDb,
Name: me.Name,
Children: authCertTags,
}
}

View File

@@ -294,7 +294,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, update *sqlparser.Updat
maxRec := 200
nowRec := 0
res := make([]map[string]any, 0)
err = dbConn.WalkQueryRows(ctx, selectSql, func(row map[string]any, columns []*dbi.QueryColumn) error {
_, err = dbConn.WalkQueryRows(ctx, selectSql, func(row map[string]any, columns []*dbi.QueryColumn) error {
nowRec++
res = append(res, row)
if nowRec == maxRec {

View File

@@ -277,7 +277,7 @@ func (app *dbTransferAppImpl) transferData(ctx context.Context, logId uint64, ta
logExtraKey := fmt.Sprintf("`%s` 当前已迁移数据量: ", tableName)
// 游标查询源表数据,并批量插入目标表
err = srcConn.WalkTableRows(context.Background(), tableName, func(row map[string]any, columns []*dbi.QueryColumn) error {
_, err = srcConn.WalkTableRows(context.Background(), tableName, func(row map[string]any, columns []*dbi.QueryColumn) error {
total++
rawValue := map[string]any{}
for _, column := range columns {

View File

@@ -39,12 +39,7 @@ func (d *DbConn) Query(querySql string, args ...any) ([]*QueryColumn, []map[stri
// 依次返回 列信息数组(顺序)结果map错误
func (d *DbConn) QueryContext(ctx context.Context, querySql string, args ...any) ([]*QueryColumn, []map[string]any, error) {
result := make([]map[string]any, 0, 16)
var queryColumns []*QueryColumn
err := d.WalkQueryRows(ctx, querySql, func(row map[string]any, columns []*QueryColumn) error {
if len(queryColumns) == 0 {
queryColumns = columns
}
cols, err := d.WalkQueryRows(ctx, querySql, func(row map[string]any, columns []*QueryColumn) error {
result = append(result, row)
return nil
}, args...)
@@ -53,7 +48,7 @@ func (d *DbConn) QueryContext(ctx context.Context, querySql string, args ...any)
return nil, nil, wrapSqlError(err)
}
return queryColumns, result, nil
return cols, result, nil
}
// 将查询结果映射至struct可具体参考sqlx库
@@ -73,12 +68,12 @@ func (d *DbConn) Query2Struct(execSql string, dest any) error {
}
// WalkQueryRows 游标方式遍历查询结果集, walkFn返回error不为nil, 则跳出遍历并取消查询
func (d *DbConn) WalkQueryRows(ctx context.Context, querySql string, walkFn WalkQueryRowsFunc, args ...any) error {
func (d *DbConn) WalkQueryRows(ctx context.Context, querySql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
return walkQueryRows(ctx, d.db, querySql, walkFn, args...)
}
// WalkTableRows 游标方式遍历指定表的结果集, walkFn返回error不为nil, 则跳出遍历并取消查询
func (d *DbConn) WalkTableRows(ctx context.Context, tableName string, walkFn WalkQueryRowsFunc) error {
func (d *DbConn) WalkTableRows(ctx context.Context, tableName string, walkFn WalkQueryRowsFunc) ([]*QueryColumn, error) {
return d.WalkQueryRows(ctx, fmt.Sprintf("SELECT * FROM %s", tableName), walkFn)
}
@@ -152,13 +147,13 @@ func (d *DbConn) Close() {
}
// 游标方式遍历查询rows, walkFn error不为nil, 则跳出遍历
func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn WalkQueryRowsFunc, args ...any) error {
func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
cancelCtx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
rows, err := db.QueryContext(cancelCtx, selectSql, args...)
if err != nil {
return err
return nil, err
}
// rows对象一定要close掉如果出错不关掉则会很迅速的达到设置最大连接数
// 后面的链接过来直接报错或拒绝,实际上也没有起效果
@@ -166,7 +161,7 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
colTypes, err := rows.ColumnTypes()
if err != nil {
return err
return nil, err
}
lenCols := len(colTypes)
// 列名用于前端表头名称按照数据库与查询字段顺序显示
@@ -189,7 +184,7 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
for rows.Next() {
// 不Scan也会导致等待该链接实际处于未工作的状态然后也会导致连接数迅速达到最大
if err := rows.Scan(scans...); err != nil {
return err
return cols, err
}
// 每行数据
rowData := make(map[string]any, lenCols)
@@ -200,11 +195,11 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
if err = walkFn(rowData, cols); err != nil {
logx.ErrorfContext(ctx, "[%s]游标遍历查询结果集出错, 退出遍历: %s", selectSql, err.Error())
cancelFunc()
return err
return cols, err
}
}
return nil
return cols, nil
}
// 将查询的值转为对应列类型的实际值,不全部转为字符串

View File

@@ -2,10 +2,12 @@ package entity
// InstanceQuery 数据库实例查询
type InstanceQuery struct {
Id uint64 `json:"id" form:"id"`
Name string `json:"name" form:"name"`
Code string `json:"code" form:"code"`
Host string `json:"host" form:"host"`
Id uint64 `json:"id" form:"id"`
Name string `json:"name" form:"name"`
Code string `json:"code" form:"code"`
Host string `json:"host" form:"host"`
TagPath string `json:"host" form:"tagPath"`
Codes []string
}
type DataSyncTaskQuery struct {

View File

@@ -22,6 +22,7 @@ func (d *instanceRepoImpl) GetInstanceList(condition *entity.InstanceQuery, page
Eq("id", condition.Id).
Eq("host", condition.Host).
Like("name", condition.Name).
Like("code", condition.Code)
Like("code", condition.Code).
In("code", condition.Codes)
return gormx.PageQuery(qd, pageParam, toEntity)
}

View File

@@ -26,7 +26,7 @@ func InitInstanceRouter(router *gin.RouterGroup) {
req.NewGet(":instanceId", d.GetInstance),
// 获取数据库实例的所有数据库名
req.NewGet(":instanceId/databases", d.GetDatabaseNames),
req.NewPost("/databases", d.GetDatabaseNames),
req.NewGet(":instanceId/server-info", d.GetDbServer),

View File

@@ -9,21 +9,17 @@ import (
)
type Dashbord struct {
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
MachineApp application.Machine `inject:""`
TagTreeApp tagapp.TagTree `inject:""`
MachineApp application.Machine `inject:""`
}
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
machienAuthCerts := m.ResourceAuthCertApp.GetAccountAuthCert(accountId, tagentity.TagTypeMachineAuthCert)
machineCodes := collx.ArrayMap(machienAuthCerts, func(ac *tagentity.ResourceAuthCert) string {
return ac.ResourceCode
})
machienNum := len(collx.ArrayDeduplicate(machineCodes))
tagCodePaths := m.TagTreeApp.GetAccountTagCodePaths(accountId, tagentity.TagTypeMachineAuthCert, "")
machineCodes := tagentity.GetCodeByPath(tagentity.TagTypeMachine, tagCodePaths...)
rc.ResData = collx.M{
"machineNum": machienNum,
"machineNum": len(machineCodes),
}
}

View File

@@ -10,8 +10,8 @@ type MachineForm struct {
Ip string `json:"ip" binding:"required"` // IP地址
Port int `json:"port" binding:"required"` // 端口号
TagId []uint64 `json:"tagId" binding:"required"`
AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts" binding:"required"` // 资产授权凭证信息列表
TagCodePaths []string `json:"tagCodePaths" binding:"required"`
AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts" binding:"required"` // 资产授权凭证信息列表
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id

View File

@@ -42,16 +42,14 @@ type Machine struct {
func (m *Machine) Machines(rc *req.Ctx) {
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery))
authCerts := m.ResourceAuthCertApp.GetAccountAuthCert(rc.GetLoginAccount().Id, tagentity.TagTypeMachineAuthCert, condition.TagPath)
// 不存在可操作的授权凭证,即没有可操作数据
if len(authCerts) == 0 {
tagCodePaths := m.TagApp.GetAccountTagCodePaths(rc.GetLoginAccount().Id, tagentity.TagTypeMachineAuthCert, condition.TagPath)
// 不存在可操作的机器-授权凭证标签,即没有可操作数据
if len(tagCodePaths) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
machineCodes := collx.ArrayMap(authCerts, func(ac *tagentity.ResourceAuthCert) string {
return ac.ResourceCode
})
machineCodes := tagentity.GetCodeByPath(tagentity.TagTypeMachine, tagCodePaths...)
condition.Codes = collx.ArrayDeduplicate(machineCodes)
var machinevos []*vo.MachineVO
@@ -68,7 +66,7 @@ func (m *Machine) Machines(rc *req.Ctx) {
})...)
// 填充授权凭证信息
m.ResourceAuthCertApp.FillAuthCertByAcs(authCerts, collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
m.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodeByPath(tagentity.TagTypeMachineAuthCert, tagCodePaths...), collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
return mvo
})...)
@@ -99,9 +97,9 @@ func (m *Machine) SaveMachine(rc *req.Ctx) {
rc.ReqParam = machineForm
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, &application.SaveMachineParam{
Machine: me,
TagIds: machineForm.TagId,
AuthCerts: machineForm.AuthCerts,
Machine: me,
TagCodePaths: machineForm.TagCodePaths,
AuthCerts: machineForm.AuthCerts,
}))
}

View File

@@ -17,12 +17,13 @@ import (
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/scheduler"
"mayfly-go/pkg/utils/collx"
)
type SaveMachineParam struct {
Machine *entity.Machine
TagIds []uint64
AuthCerts []*tagentity.ResourceAuthCert
Machine *entity.Machine
TagCodePaths []string
AuthCerts []*tagentity.ResourceAuthCert
}
type Machine interface {
@@ -81,7 +82,7 @@ func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pagePara
func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachineParam) error {
me := param.Machine
tagIds := param.TagIds
tagCodePaths := param.TagCodePaths
authCerts := param.AuthCerts
resourceType := tagentity.TagTypeMachine
@@ -107,29 +108,20 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
// 新增机器,默认启用状态
me.Status = entity.MachineStatusEnable
if err := m.Tx(ctx, func(ctx context.Context) error {
return m.Tx(ctx, func(ctx context.Context) error {
return m.Insert(ctx, me)
}, func(ctx context.Context) error {
return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: me.Code,
Type: resourceType,
ParentTagIds: tagIds,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: me.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
})
}); err != nil {
return err
}
// 事务问题,导致可能删除或新增的标签数据不可见,造成数据错误,故单独执行。若万一失败,可授权凭证管理处添加
return m.resourceAuthCertApp.RelateAuthCert2ResourceTag(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: me.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
}, func(ctx context.Context) error {
return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceTag: m.genMachineResourceTag(me, authCerts),
ParentTagCodePaths: tagCodePaths,
})
})
}
@@ -146,29 +138,23 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
mcm.DeleteCli(me.Id)
// 防止误传修改
me.Code = ""
if err := m.Tx(ctx, func(ctx context.Context) error {
return m.Tx(ctx, func(ctx context.Context) error {
return m.UpdateById(ctx, me)
}, func(ctx context.Context) error {
return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: oldMachine.Code,
Type: resourceType,
ParentTagIds: tagIds,
})
}); err != nil {
return err
}
return m.Tx(ctx, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: oldMachine.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert2ResourceTag(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: oldMachine.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
if oldMachine.Name != me.Name {
if err := m.tagApp.UpdateTagName(ctx, tagentity.TagTypeMachine, oldMachine.Code, me.Name); err != nil {
return err
}
}
return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceTag: m.genMachineResourceTag(oldMachine, authCerts),
ParentTagCodePaths: tagCodePaths,
})
})
}
@@ -230,19 +216,16 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
return m.DeleteById(ctx, id)
}, func(ctx context.Context) error {
return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: machine.Code,
Type: resourceType,
ResourceTag: &tagapp.ResourceTag{
Code: machine.Code,
Type: tagentity.TagTypeMachine,
},
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: machine.Code,
ResourceType: resourceType,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert2ResourceTag(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: machine.Code,
ResourceType: resourceType,
})
})
}
@@ -362,6 +345,7 @@ func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAu
mi.EnableRecorder = me.EnableRecorder
mi.Protocol = me.Protocol
mi.AuthCertName = authCert.Name
mi.Username = authCert.Username
mi.Password = authCert.Ciphertext
mi.Passphrase = authCert.GetExtraString(tagentity.ExtraKeyPassphrase)
@@ -377,3 +361,20 @@ func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAu
}
return mi, nil
}
func (m *machineAppImpl) genMachineResourceTag(me *entity.Machine, authCerts []*tagentity.ResourceAuthCert) *tagapp.ResourceTag {
authCertTags := collx.ArrayMap[*tagentity.ResourceAuthCert, *tagapp.ResourceTag](authCerts, func(val *tagentity.ResourceAuthCert) *tagapp.ResourceTag {
return &tagapp.ResourceTag{
Code: val.Name,
Name: val.Username,
Type: tagentity.TagTypeMachineAuthCert,
}
})
return &tagapp.ResourceTag{
Code: me.Code,
Type: tagentity.TagTypeMachine,
Name: me.Name,
Children: authCertTags,
}
}

View File

@@ -2,11 +2,11 @@ package form
type Mongo struct {
Id uint64 `json:"id"`
Code uint64 `json:"code" binding:"required"`
Code string `json:"code" binding:"required"`
Uri string `binding:"required" json:"uri"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Name string `binding:"required" json:"name"`
TagId []uint64 `binding:"required" json:"tagId"`
TagCodePaths []string `binding:"required" json:"tagCodePaths"`
}
type MongoCommand struct {

View File

@@ -67,7 +67,7 @@ func (m *Mongo) Save(rc *req.Ctx) {
}(form.Uri)
rc.ReqParam = form
biz.ErrIsNil(m.MongoApp.SaveMongo(rc.MetaCtx, mongo, form.TagId...))
biz.ErrIsNil(m.MongoApp.SaveMongo(rc.MetaCtx, mongo, form.TagCodePaths...))
}
func (m *Mongo) DeleteMongo(rc *req.Ctx) {

View File

@@ -21,7 +21,7 @@ type Mongo interface {
TestConn(entity *entity.Mongo) error
SaveMongo(ctx context.Context, entity *entity.Mongo, tagIds ...uint64) error
SaveMongo(ctx context.Context, entity *entity.Mongo, tagCodePaths ...string) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -59,10 +59,10 @@ func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
return d.DeleteById(ctx, id)
},
func(ctx context.Context) error {
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{ResourceTag: &tagapp.ResourceTag{
Type: tagentity.TagTypeMongo,
Code: mongoEntity.Code,
})
}})
})
}
@@ -75,7 +75,7 @@ func (d *mongoAppImpl) TestConn(me *entity.Mongo) error {
return nil
}
func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ...uint64) error {
func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagCodePaths ...string) error {
oldMongo := &entity.Mongo{Name: m.Name, SshTunnelMachineId: m.SshTunnelMachineId}
err := d.GetBy(oldMongo)
@@ -91,9 +91,11 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
return d.Insert(ctx, m)
}, func(ctx context.Context) error {
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagentity.TagTypeMongo,
Code: m.Code,
ParentTagIds: tagIds,
ResourceTag: &tagapp.ResourceTag{
Type: tagentity.TagTypeMongo,
Code: m.Code,
},
ParentTagCodePaths: tagCodePaths,
})
})
}
@@ -113,10 +115,18 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
return d.Tx(ctx, func(ctx context.Context) error {
return d.UpdateById(ctx, m)
}, func(ctx context.Context) error {
if oldMongo.Name != m.Name {
if err := d.tagApp.UpdateTagName(ctx, tagentity.TagTypeMachine, oldMongo.Code, m.Name); err != nil {
return err
}
}
return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagentity.TagTypeMongo,
Code: oldMongo.Code,
ParentTagIds: tagIds,
ResourceTag: &tagapp.ResourceTag{
Type: tagentity.TagTypeMongo,
Code: oldMongo.Code,
},
ParentTagCodePaths: tagCodePaths,
})
})
}

View File

@@ -10,7 +10,7 @@ type Redis struct {
Mode string `json:"mode"`
Db string `json:"db"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
TagId []uint64 `binding:"required" json:"tagId"`
TagCodePaths []string `binding:"required" json:"tagCodePaths"`
Remark string `json:"remark"`
FlowProcdefKey string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
}

View File

@@ -55,8 +55,7 @@ func (r *Redis) TestConn(rc *req.Ctx) {
redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis))
biz.ErrIsNil(r.RedisApp.TestConn(&application.SaveRedisParam{
Redis: redis,
TagIds: form.TagId,
Redis: redis,
AuthCert: &tagentity.ResourceAuthCert{
Name: fmt.Sprintf("redis_%s_ac", redis.Code),
Username: form.Username,
@@ -76,8 +75,8 @@ func (r *Redis) Save(rc *req.Ctx) {
rc.ReqParam = form
redisParam := &application.SaveRedisParam{
Redis: redis,
TagIds: form.TagId,
Redis: redis,
TagCodePaths: form.TagCodePaths,
AuthCert: &tagentity.ResourceAuthCert{
Name: fmt.Sprintf("redis_%s_ac", redis.Code),
Username: form.Username,

View File

@@ -24,9 +24,9 @@ import (
)
type SaveRedisParam struct {
Redis *entity.Redis
AuthCert *tagentity.ResourceAuthCert
TagIds []uint64
Redis *entity.Redis
AuthCert *tagentity.ResourceAuthCert
TagCodePaths []string
}
type RunCmdParam struct {
@@ -109,7 +109,7 @@ func (r *redisAppImpl) TestConn(param *SaveRedisParam) error {
func (r *redisAppImpl) SaveRedis(ctx context.Context, param *SaveRedisParam) error {
re := param.Redis
tagIds := param.TagIds
tagCodePaths := param.TagCodePaths
// 查找是否存在该库
oldRedis := &entity.Redis{
Host: re.Host,
@@ -129,9 +129,12 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *SaveRedisParam) err
return r.Insert(ctx, re)
}, func(ctx context.Context) error {
return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagentity.TagTypeRedis,
Code: re.Code,
ParentTagIds: tagIds,
ResourceTag: &tagapp.ResourceTag{
Type: tagentity.TagTypeRedis,
Code: re.Code,
Name: re.Name,
},
ParentTagCodePaths: tagCodePaths,
})
}, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
@@ -162,10 +165,18 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *SaveRedisParam) err
return r.Tx(ctx, func(ctx context.Context) error {
return r.UpdateById(ctx, re)
}, func(ctx context.Context) error {
if oldRedis.Name != re.Name {
if err := r.tagApp.UpdateTagName(ctx, tagentity.TagTypeMachine, oldRedis.Code, re.Name); err != nil {
return err
}
}
return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagentity.TagTypeRedis,
Code: oldRedis.Code,
ParentTagIds: tagIds,
ResourceTag: &tagapp.ResourceTag{
Type: tagentity.TagTypeRedis,
Code: oldRedis.Code,
Name: re.Name,
},
ParentTagCodePaths: tagCodePaths,
})
}, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
@@ -192,8 +203,10 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
return r.DeleteById(ctx, id)
}, func(ctx context.Context) error {
return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagentity.TagTypeRedis,
Code: re.Code,
ResourceTag: &tagapp.ResourceTag{
Type: tagentity.TagTypeRedis,
Code: re.Code,
},
})
}, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{

View File

@@ -0,0 +1,9 @@
package form
import "mayfly-go/internal/tag/domain/entity"
type TagTree struct {
*entity.TagTree
Pid uint64 `json:"pid"`
}

View File

@@ -3,6 +3,7 @@ package api
import (
"fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/tag/api/form"
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
@@ -15,8 +16,6 @@ import (
type TagTree struct {
TagTreeApp application.TagTree `inject:""`
ResourceAuthCertApp application.ResourceAuthCert `inject:""`
}
func (p *TagTree) GetTagTree(rc *req.Ctx) {
@@ -71,12 +70,12 @@ func (p *TagTree) ListByQuery(rc *req.Ctx) {
}
func (p *TagTree) SaveTagTree(rc *req.Ctx) {
tagTree := &entity.TagTree{}
req.BindJsonAndValid(rc, tagTree)
tagForm := &form.TagTree{}
tagTree := req.BindJsonAndCopyTo(rc, tagForm, new(entity.TagTree))
rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, code: %s", tagTree.Id, tagTree.Name, tagTree.Code)
biz.ErrIsNil(p.TagTreeApp.Save(rc.MetaCtx, tagTree))
biz.ErrIsNil(p.TagTreeApp.SaveTag(rc.MetaCtx, tagForm.Pid, tagTree))
}
func (p *TagTree) DelTagTree(rc *req.Ctx) {
@@ -103,14 +102,12 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
tagPath := rc.Query("tagPath")
accountId := rc.GetLoginAccount().Id
machienAuthCerts := p.ResourceAuthCertApp.GetAccountAuthCert(accountId, entity.TagTypeMachineAuthCert, tagPath)
machineCodes := collx.ArrayMap(machienAuthCerts, func(ac *entity.ResourceAuthCert) string {
return ac.ResourceCode
})
machineCodes := entity.GetCodeByPath(entity.TagTypeMachine, p.TagTreeApp.GetAccountTagCodePaths(accountId, entity.TagTypeMachineAuthCert, tagPath)...)
dbCodes := entity.GetCodeByPath(entity.TagTypeDb, p.TagTreeApp.GetAccountTagCodePaths(accountId, entity.TagTypeDbName, tagPath)...)
rc.ResData = collx.M{
"machine": len(collx.ArrayDeduplicate(machineCodes)),
"db": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeDb, tagPath)),
"machine": len(machineCodes),
"db": len(dbCodes),
"redis": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeRedis, tagPath)),
"mongo": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, tagPath)),
}

View File

@@ -2,38 +2,38 @@ package vo
import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/utils/collx"
)
type TagTreeVOS []*entity.TagTree
type TagTreeItem struct {
*entity.TagTree
Children []TagTreeItem `json:"children"`
Children []*TagTreeItem `json:"children"`
}
func (m *TagTreeVOS) ToTrees(pid uint64) []TagTreeItem {
var resourceTree []TagTreeItem
list := m.findChildren(pid)
if len(list) == 0 {
return resourceTree
func (m *TagTreeVOS) ToTrees(pid uint64) []*TagTreeItem {
var ttis []*TagTreeItem
if len(*m) == 0 {
return ttis
}
for _, v := range list {
Children := m.ToTrees(v.Id)
resourceTree = append(resourceTree, TagTreeItem{v, Children})
}
ttis = collx.ArrayMap(*m, func(tr *entity.TagTree) *TagTreeItem { return &TagTreeItem{TagTree: tr} })
tagMap := collx.ArrayToMap(ttis, func(item *TagTreeItem) string {
return item.CodePath
})
return resourceTree
}
func (m *TagTreeVOS) findChildren(pid uint64) []*entity.TagTree {
child := []*entity.TagTree{}
for _, v := range *m {
if v.Pid == pid {
child = append(child, v)
for _, node := range ttis {
// 根节点
if node.IsRoot() {
continue
}
parentCodePath := node.GetParentPath(0)
parentNode := tagMap[parentCodePath]
if parentNode != nil {
parentNode.Children = append(parentNode.Children, node)
}
}
return child
return collx.ArrayFilter(ttis, func(tti *TagTreeItem) bool { return tti.IsRoot() })
}

View File

@@ -26,9 +26,6 @@ type ResourceAuthCert interface {
// RelateAuthCert 关联资源授权凭证信息
RelateAuthCert(ctx context.Context, param *RelateAuthCertParam) error
// RelateAuthCert2ResourceTag 关联授权凭证标签至指定资源标签下
RelateAuthCert2ResourceTag(ctx context.Context, param *RelateAuthCertParam) error
// SaveAuthCert 保存授权凭证信息
SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error
@@ -40,9 +37,6 @@ type ResourceAuthCert interface {
// GetResourceAuthCert 获取资源授权凭证,默认获取特权账号,若没有则返回第一个
GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error)
// GetAccountAuthCert 获取账号有权限操作的授权凭证信息
GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert
// FillAuthCertByAcs 根据授权凭证列表填充资源的授权凭证信息
// @param authCerts 授权凭证列表
// @param resources 实现了entity.IAuthCert接口的资源信息
@@ -52,6 +46,9 @@ type ResourceAuthCert interface {
// @param resourceType 资源类型
// @param resources 实现了entity.IAuthCert接口的资源信息
FillAuthCert(resourceType int8, resources ...entity.IAuthCert)
// FillAuthCertByAcNames 根据授权凭证名称填充资源对应的凭证信息
FillAuthCertByAcNames(authCertNames []string, resources ...entity.IAuthCert)
}
type resourceAuthCertAppImpl struct {
@@ -166,7 +163,7 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
// 如果修改了用户名且该凭证关联至标签则需要更新对应的标签名资源授权凭证类型的标签名为username
if oldAuthCert.Username != unmodifyAc.Username && acTagType != 0 {
r.updateAuthCertTagName(ctx, unmodify, acTagType, unmodifyAc.Username)
r.tagTreeApp.UpdateTagName(ctx, acTagType, unmodify, unmodifyAc.Username)
}
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodify)
if err := r.UpdateById(ctx, unmodifyAc); err != nil {
@@ -178,17 +175,6 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
return nil
}
func (r *resourceAuthCertAppImpl) RelateAuthCert2ResourceTag(ctx context.Context, param *RelateAuthCertParam) error {
return r.tagTreeApp.RelateTagsByCodeAndType(ctx, &RelateTagsByCodeAndTypeParam{
ParentTagCode: param.ResourceCode,
ParentTagType: param.ResourceType,
TagType: GetResourceAuthCertTagType(param.ResourceType),
Tags: collx.ArrayMap(param.AuthCerts, func(rac *entity.ResourceAuthCert) ITag {
return rac
}),
})
}
func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
if rac.Id == 0 {
return r.addAuthCert(ctx, rac)
@@ -258,27 +244,6 @@ func (r *resourceAuthCertAppImpl) GetResourceAuthCert(resourceType entity.TagTyp
return r.decryptAuthCert(resourceAuthCerts[0])
}
func (r *resourceAuthCertAppImpl) GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert {
// 获取用户有权限操作的授权凭证资源标签
tagQuery := &entity.TagTreeQuery{
Type: authCertTagType,
CodePathLikes: tagPath,
}
authCertTags := r.tagTreeApp.GetAccountTags(accountId, tagQuery)
// 获取所有授权凭证名称
authCertNames := collx.ArrayMap(authCertTags, func(tag *entity.TagTree) string {
return tag.Code
})
var authCerts []*entity.ResourceAuthCert
r.ListByWheres(collx.M{
"name in ?": collx.ArrayDeduplicate(authCertNames),
}, &authCerts)
return authCerts
}
func (r *resourceAuthCertAppImpl) FillAuthCertByAcs(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert) {
if len(resources) == 0 || len(authCerts) == 0 {
return
@@ -305,6 +270,12 @@ func (r *resourceAuthCertAppImpl) FillAuthCertByAcs(authCerts []*entity.Resource
}
}
func (r *resourceAuthCertAppImpl) FillAuthCertByAcNames(authCertNames []string, resources ...entity.IAuthCert) {
var acs []*entity.ResourceAuthCert
r.ListByWheres(collx.M{"name in ?": authCertNames}, &acs)
r.FillAuthCertByAcs(acs, resources...)
}
func (r *resourceAuthCertAppImpl) FillAuthCert(resourceType int8, resources ...entity.IAuthCert) {
if len(resources) == 0 {
return
@@ -336,17 +307,17 @@ func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.R
// 资源对应的授权凭证标签类型若为0则说明该资源不需要关联至资源tagTree
authCertTagType := GetResourceAuthCertTagType(entity.TagType(resourceType))
var resourceTagIds []uint64
var resourceTagCodePaths []string
// 如果该资源存在对应的授权凭证标签类型则说明需要关联至tagTree否则直接从授权凭证库中验证资源编号是否正确即可一个资源最少有一个授权凭证
if authCertTagType != 0 {
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
r.tagTreeApp.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &resourceTags)
// 资源标签id相当于父tag id
resourceTagIds = collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 {
return tag.Id
// 资源标签tagPath相当于父tag
resourceTagCodePaths = collx.ArrayMap(resourceTags, func(tag *entity.TagTree) string {
return tag.CodePath
})
if len(resourceTagIds) == 0 {
if len(resourceTagCodePaths) == 0 {
return errorx.NewBiz("资源标签不存在[%s], 请检查资源编号是否正确", resourceCode)
}
} else {
@@ -362,13 +333,15 @@ func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.R
return r.Tx(ctx, func(ctx context.Context) error {
// 若存在需要关联到的资源标签,则关联到对应的资源标签下
if len(resourceTagIds) > 0 {
logx.DebugfContext(ctx, "[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, rac.Name, resourceTagIds)
if len(resourceTagCodePaths) > 0 {
logx.DebugfContext(ctx, "[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, rac.Name, resourceTagCodePaths)
return r.tagTreeApp.SaveResourceTag(ctx, &SaveResourceTagParam{
Code: rac.Name,
Type: GetResourceAuthCertTagType(entity.TagType(resourceType)),
Name: rac.Username,
ParentTagIds: resourceTagIds,
ResourceTag: &ResourceTag{
Code: rac.Name,
Type: GetResourceAuthCertTagType(entity.TagType(resourceType)),
Name: rac.Username,
},
ParentTagCodePaths: resourceTagCodePaths,
})
}
return nil
@@ -406,7 +379,7 @@ func (r *resourceAuthCertAppImpl) updateAuthCert(ctx context.Context, rac *entit
if rac.Username != oldRac.Username && rac.ResourceType == int8(entity.TagTypeMachine) {
authCertTagType := GetResourceAuthCertTagType(entity.TagType(oldRac.ResourceType))
if authCertTagType != 0 {
if err := r.updateAuthCertTagName(ctx, oldRac.Name, authCertTagType, rac.Username); err != nil {
if err := r.tagTreeApp.UpdateTagName(ctx, authCertTagType, oldRac.Name, rac.Username); err != nil {
return errorx.NewBiz("同步更新授权凭证标签名称失败")
}
}
@@ -425,11 +398,6 @@ func (r *resourceAuthCertAppImpl) updateAuthCert(ctx context.Context, rac *entit
return r.UpdateById(ctx, rac)
}
// updateAuthCertTagName 同步更新授权凭证的标签名,防止标签展示不一致
func (r *resourceAuthCertAppImpl) updateAuthCertTagName(ctx context.Context, authCertName string, autyCertTagType entity.TagType, newTagName string) error {
return r.tagTreeApp.UpdateByWheres(ctx, &entity.TagTree{Name: newTagName}, collx.M{"code = ?": authCertName, "type = ?": autyCertTagType})
}
// 解密授权凭证信息
func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
@@ -458,6 +426,10 @@ func GetResourceAuthCertTagType(resourceType entity.TagType) entity.TagType {
return entity.TagTypeMachineAuthCert
}
if resourceType == entity.TagTypeDb {
return entity.TagTypeDbAuthCert
}
// 该资源不存在对应的授权凭证标签即tag_tree不关联该资源的授权凭证
return 0
}

View File

@@ -14,30 +14,26 @@ import (
"strings"
)
// 标签接口,实现了该接口的结构体默认都可以当成标签树的一种标签
type ITag interface {
// 资源标签code
GetCode() string
// 保存资源标签参数
type SaveResourceTagParam struct {
ParentTagCodePaths []string // 关联标签,空数组则为删除该资源绑定的标签
// 资源标签
GetName() string
ResourceTag *ResourceTag // 资源标签信息
}
// 保存标签参数
type SaveResourceTagParam struct {
type ResourceTag struct {
Code string
Name string
Type entity.TagType
Name string
ParentTagIds []uint64 // 关联标签,空数组则为删除该资源绑定的标签
Children []*ResourceTag // 子资源标签
}
type RelateTagsByCodeAndTypeParam struct {
ParentTagCode string // 父标签编号
ParentTagType entity.TagType // 父标签类型
TagType entity.TagType // 要关联的标签类型
Tags []ITag // 要关联的标签数组
Tags []*ResourceTag // 要关联的标签数组
}
type DelResourceTagParam struct {
@@ -45,8 +41,6 @@ type DelResourceTagParam struct {
ResourceCode string
ResourceType entity.TagType
Pid uint64 //父标签 pid
// 要删除的子节点类型,若存在值,则为删除资源标签下的指定类型的子标签
ChildType entity.TagType
}
@@ -56,37 +50,46 @@ type TagTree interface {
ListByQuery(condition *entity.TagTreeQuery, toEntity any)
Save(ctx context.Context, tt *entity.TagTree) error
SaveTag(ctx context.Context, pid uint64, tt *entity.TagTree) error
Delete(ctx context.Context, id uint64) error
// 获取指定账号有权限操作的标签列表
// GetAccountTags 获取指定账号有权限操作的标签列表
// @param accountId 账号id
// @param query 查询条件
GetAccountTags(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree
// 获取指定账号有权限操作的标签codes
// GetAccountTagCodes 获取指定账号有权限操作的标签codes
GetAccountTagCodes(accountId uint64, resourceType int8, tagPath string) []string
// GetAccountTagCodePaths 获取指定账号有权限操作的codePaths
GetAccountTagCodePaths(accountId uint64, tagType entity.TagType, tagPath string) []string
// SaveResourceTag 保存资源类型标签
SaveResourceTag(ctx context.Context, param *SaveResourceTagParam) error
// RelateTagsByCodeAndType 将指定标签数组关联至满足指定标签类型和标签code的标签下
RelateTagsByCodeAndType(ctx context.Context, param *RelateTagsByCodeAndTypeParam) error
// UpdateTagName 根据标签类型与code更新对应标签名
UpdateTagName(ctx context.Context, tagType entity.TagType, tagCode string, tagName string) error
// UpdateParentTagCode 更新指定类型标签的所有父标签编号
UpdateParentTagCode(ctx context.Context, tagType entity.TagType, tagCode string, newParentCode string, newParentName string) error
// DeleteTagByParam 删除标签,会删除该标签下所有子标签信息以及团队关联的标签信息
DeleteTagByParam(ctx context.Context, param *DelResourceTagParam) error
// 根据标签类型和标签code获取对应的标签路径列表
ListTagPathByTypeAndCode(resourceType int8, resourceCode string) []string
// 根据账号id获取其可访问标签信息
// ListTagByAccountId 根据账号id获取其可访问标签信息
ListTagByAccountId(accountId uint64) []string
// 账号是否有权限访问该标签关联的资源信息
// CanAccess 账号是否有权限访问该标签关联的资源信息
CanAccess(accountId uint64, tagPath ...string) error
// 填充资源的标签信息
// FillTagInfo 填充资源的标签信息
FillTagInfo(resourceTagType entity.TagType, resources ...entity.ITagResource)
}
@@ -101,15 +104,15 @@ func (p *tagTreeAppImpl) InjectTagTreeRepo(tagTreeRepo repository.TagTree) {
p.Repo = tagTreeRepo
}
func (p *tagTreeAppImpl) Save(ctx context.Context, tag *entity.TagTree) error {
func (p *tagTreeAppImpl) SaveTag(ctx context.Context, pid uint64, tag *entity.TagTree) error {
accountId := contextx.GetLoginAccount(ctx).Id
// 新建项目树节点信息
if tag.Id == 0 {
if strings.Contains(tag.Code, entity.CodePathSeparator) {
return errorx.NewBiz("标识符不能包含'/'")
}
if tag.Pid != 0 {
parentTag, err := p.GetById(new(entity.TagTree), tag.Pid)
if pid != 0 {
parentTag, err := p.GetById(new(entity.TagTree), pid)
if err != nil {
return errorx.NewBiz("父节点不存在")
}
@@ -214,129 +217,59 @@ func (p *tagTreeAppImpl) GetAccountTagCodes(accountId uint64, resourceType int8,
return collx.MapKeys(code2Resource)
}
func (p *tagTreeAppImpl) GetAccountTagCodePaths(accountId uint64, tagType entity.TagType, tagPath string) []string {
tagResources := p.GetAccountTags(accountId, &entity.TagTreeQuery{Type: tagType, CodePathLikes: []string{tagPath}})
// resouce code去重
code2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(val *entity.TagTree) string {
return val.CodePath
})
return collx.MapKeys(code2Resource)
}
func (p *tagTreeAppImpl) SaveResourceTag(ctx context.Context, param *SaveResourceTagParam) error {
code := param.Code
tagType := entity.TagType(param.Type)
name := param.Name
tagIds := param.ParentTagIds
code := param.ResourceTag.Code
tagType := param.ResourceTag.Type
parentTagCodePaths := param.ParentTagCodePaths
if code == "" {
return errorx.NewBiz("资源编号不能为空")
return errorx.NewBiz("保存资源标签失败: 资源编号不能为空")
}
if tagType == 0 {
return errorx.NewBiz("资源类型不能为空")
return errorx.NewBiz("保存资源标签失败:资源类型不能为空")
}
// 如果tagIds为空数组则为删除该资源标签
if len(tagIds) == 0 {
if len(parentTagCodePaths) == 0 {
return p.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceType: tagType,
ResourceCode: code,
})
}
if name == "" {
name = code
// 获取所有关联的子标签
var parentTags []*entity.TagTree
p.ListByWheres(collx.M{
"code_path in ?": parentTagCodePaths,
}, &parentTags)
if len(parentTags) == 0 || len(parentTags) != len(parentTagCodePaths) {
return errorx.NewBiz("保存资源标签失败: 存在错误的关联标签")
}
newTags := p.toTags(parentTags, param.ResourceTag)
var oldParentTagTree []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: tagType, Code: code}, &oldParentTagTree)
// 该资源对应的旧资源标签信息
var oldTagTree []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: tagType, Code: code}, &oldTagTree)
var addTagIds, delTagIds []uint64
if len(oldTagTree) == 0 {
addTagIds = tagIds
} else {
oldTagIds := collx.ArrayMap(oldTagTree, func(tag *entity.TagTree) uint64 {
return tag.Pid
})
addTagIds, delTagIds, _ = collx.ArrayCompare[uint64](tagIds, oldTagIds)
}
if len(addTagIds) > 0 {
addTagResource := make([]*entity.TagTree, 0)
for _, tagId := range addTagIds {
tag, err := p.GetById(new(entity.TagTree), tagId)
if err != nil {
return errorx.NewBiz("存在错误标签id")
}
addTagResource = append(addTagResource, &entity.TagTree{
Pid: tagId,
Code: code,
Type: tagType,
Name: name,
CodePath: fmt.Sprintf("%s%d%s%s%s", tag.CodePath, tagType, entity.CodePathResourceSeparator, code, entity.CodePathSeparator), // tag1/tag2/1|resourceCode1/11|resourceCode2
})
}
if err := p.BatchInsert(ctx, addTagResource); err != nil {
return err
}
}
if len(delTagIds) > 0 {
for _, tagId := range delTagIds {
if err := p.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceType: tagType,
ResourceCode: code,
Pid: tagId,
}); err != nil {
return err
}
}
}
return nil
}
func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *RelateTagsByCodeAndTypeParam) error {
parentTagCode := param.ParentTagCode
parentTagType := param.ParentTagType
tagType := param.TagType
// 如果资源为,则表示清楚关联
if len(param.Tags) == 0 {
// 删除该资源下的所有指定类型的资源
return p.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceCode: parentTagCode,
ResourceType: param.ParentTagType,
ChildType: tagType,
})
}
// 获取满足指定编号与类型的所有标签信息
var parentTags []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: parentTagType, Code: parentTagCode}, &parentTags)
// 标签id相当于需要关联的标签数组的父tag id
parentTagIds := collx.ArrayMap(parentTags, func(tag *entity.TagTree) uint64 {
return tag.Id
})
if len(parentTagIds) == 0 {
return errorx.NewBiz("不存在满足[type=%d, code=%s]的标签", parentTagType, parentTagCode)
}
var oldChildrenTags []*entity.TagTree
// 获取该资源的所有旧的该类型的子标签
p.ListByQuery(&entity.TagTreeQuery{
CodePathLikes: collx.ArrayMap[*entity.TagTree, string](parentTags, func(val *entity.TagTree) string { return val.CodePath }),
Type: tagType,
}, &oldChildrenTags)
// 组合新的授权凭证资源标签
newTags := make([]*entity.TagTree, 0)
for _, resourceTag := range parentTags {
for _, resource := range param.Tags {
tagCode := resource.GetCode()
newTags = append(newTags, &entity.TagTree{
Pid: resourceTag.Id,
Type: tagType,
Code: tagCode,
CodePath: fmt.Sprintf("%s%d%s%s%s", resourceTag.CodePath, tagType, entity.CodePathResourceSeparator, tagCode, entity.CodePathSeparator),
Name: resource.GetName(),
})
}
if len(oldParentTagTree) > 0 {
// 获取所有旧的子标签
p.ListByQuery(&entity.TagTreeQuery{
CodePathLikes: collx.ArrayMap[*entity.TagTree, string](oldParentTagTree, func(val *entity.TagTree) string {
return val.CodePath
}),
}, &oldChildrenTags)
}
// 旧的codePath -> tag
@@ -348,8 +281,7 @@ func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *Rel
addCodePaths, delCodePaths, _ = collx.ArrayCompare(collx.MapKeys(newCodePath2Tag), collx.MapKeys(oldCodePath2Tag))
if len(addCodePaths) > 0 {
logx.DebugfContext(ctx, "RelateTags2CodeAndType[%d-%s]-新增标签[%v]", parentTagType, parentTagCode, addCodePaths)
logx.DebugfContext(ctx, "SaveResourceTag-新增标签[%v]", addCodePaths)
addTags := make([]*entity.TagTree, 0)
for _, addCodePath := range addCodePaths {
addTags = append(addTags, newCodePath2Tag[addCodePath])
@@ -360,19 +292,97 @@ func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *Rel
}
if len(delCodePaths) > 0 {
logx.DebugfContext(ctx, "RelateTags2CodeAndType[%d-%s]-删除标签[%v]", parentTagType, parentTagCode, delCodePaths)
logx.DebugfContext(ctx, "SaveResourceTag-删除标签[%v]", delCodePaths)
// 删除team关联的标签
if err := p.tagTreeTeamRepo.DeleteByWheres(ctx, collx.M{"tag_path in ?": delCodePaths}); err != nil {
return err
}
if err := p.DeleteByWheres(ctx, collx.M{"code_path in ?": delCodePaths}); err != nil {
return err
}
}
for _, delCodePath := range delCodePaths {
oldTag := oldCodePath2Tag[delCodePath]
if oldTag == nil {
continue
}
if err := p.DeleteTagByParam(ctx, &DelResourceTagParam{
Id: oldTag.Id,
}); err != nil {
return err
return nil
}
func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *RelateTagsByCodeAndTypeParam) error {
parentTagCode := param.ParentTagCode
parentTagType := param.ParentTagType
// 获取满足指定编号与类型的所有标签信息
var parentTags []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: parentTagType, Code: parentTagCode}, &parentTags)
// 标签codePaths相当于需要关联的标签数组的父tag
parentTagCodePaths := collx.ArrayMap(parentTags, func(tag *entity.TagTree) string {
return tag.CodePath
})
if len(parentTagCodePaths) == 0 {
return errorx.NewBiz("不存在满足[type=%d, code=%s]的标签", parentTagType, parentTagCode)
}
for _, tag := range param.Tags {
if err := (p.SaveResourceTag(ctx, &SaveResourceTagParam{
ResourceTag: tag,
ParentTagCodePaths: parentTagCodePaths,
})); err != nil {
return err
}
}
return nil
}
func (p *tagTreeAppImpl) UpdateTagName(ctx context.Context, tagType entity.TagType, tagCode string, tagName string) error {
return p.UpdateByWheres(ctx, &entity.TagTree{Name: tagName}, collx.Kvs("code = ?", tagCode, "type = ?", tagType))
}
func (p *tagTreeAppImpl) UpdateParentTagCode(ctx context.Context, tagType entity.TagType, tagCode string, newParentCode string, newParentName string) error {
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: tagType, Code: tagCode}, &resourceTags)
if len(resourceTags) == 0 {
logx.WarnfContext(ctx, "UpdateParentTagCode-[%d-%s]标签信息不存在", tagType, tagCode)
return nil
}
// 获取该资源编号对应的所有父资源标签信息
var resourceParentTags []*entity.TagTree
p.ListByWheres(collx.Kvs("code_path in ?", collx.ArrayMap(resourceTags, func(tag *entity.TagTree) string {
return tag.GetParentPath(0)
})), &resourceParentTags)
// 获取该资源编号对应的所有子资源标签信息
var resourceChildrenTags []*entity.TagTree
p.ListByQuery(&entity.TagTreeQuery{CodePathLikes: collx.ArrayMap(resourceTags, func(tag *entity.TagTree) string {
return tag.CodePath
})}, &resourceChildrenTags)
// 更新父标签的codepath
for _, tag := range resourceChildrenTags {
pathSection := entity.GetTagPathSections(tag.CodePath)
for i, ps := range pathSection {
if ps.Type == tagType && ps.Code == tagCode {
// 将父标签编号修改为对应的新编号
pathSection[i-1].Code = newParentCode
}
}
tag.CodePath = pathSection.ToCodePath()
p.UpdateById(ctx, tag)
}
// 更新资源标签的code与codePath貌似这段可以不用先留着
for _, parentTag := range resourceParentTags {
pathSection := entity.GetTagPathSections(parentTag.CodePath)
pathSection[len(pathSection)-1].Code = newParentCode
newCodePath := pathSection.ToCodePath()
// 若新的父级标签路径已存在,则不更新该父标签,避免出现重复的标签路径
if p.CountByCond(&entity.TagTree{CodePath: newCodePath}) > 0 {
continue
}
parentTag.CodePath = newCodePath
parentTag.Code = newParentCode
parentTag.Name = newParentName
p.UpdateById(ctx, parentTag)
}
return nil
@@ -381,12 +391,12 @@ func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *Rel
func (p *tagTreeAppImpl) DeleteTagByParam(ctx context.Context, param *DelResourceTagParam) error {
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
cond := &entity.TagTree{Type: param.ResourceType, Code: param.ResourceCode, Pid: param.Pid}
cond := &entity.TagTree{Type: param.ResourceType, Code: param.ResourceCode}
cond.Id = param.Id
p.ListByCond(cond, &resourceTags)
if len(resourceTags) == 0 {
logx.DebugfContext(ctx, "TagTreeApp.DeleteResource[%d-%s]不存在可删除的标签", param.ResourceType, param.ResourceCode)
logx.DebugfContext(ctx, "TagTreeApp.DeleteTagByParam[%d-%s]不存在可删除的标签", param.ResourceType, param.ResourceCode)
return nil
}
@@ -405,7 +415,7 @@ func (p *tagTreeAppImpl) DeleteTagByParam(ctx context.Context, param *DelResourc
childrenTagIds := collx.ArrayMap(childrenTag, func(item *entity.TagTree) uint64 {
return item.Id
})
// 删除所有code_path下的子标签
// 删除code_path下的所有子标签
if err := p.DeleteByWheres(ctx, collx.M{
"id in ?": childrenTagIds,
}); err != nil {
@@ -413,7 +423,9 @@ func (p *tagTreeAppImpl) DeleteTagByParam(ctx context.Context, param *DelResourc
}
// 删除team关联的标签
return p.tagTreeTeamRepo.DeleteByWheres(ctx, collx.M{"tag_id in ?": childrenTagIds})
if err := p.tagTreeTeamRepo.DeleteByWheres(ctx, collx.M{"tag_id in ?": childrenTagIds}); err != nil {
return err
}
}
return nil
@@ -464,7 +476,7 @@ func (p *tagTreeAppImpl) FillTagInfo(resourceTagType entity.TagType, resources .
for _, tr := range tagResources {
// 赋值标签信息
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetTagPath()})
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{CodePath: tr.CodePath})
}
}
@@ -478,14 +490,52 @@ func (p *tagTreeAppImpl) Delete(ctx context.Context, id uint64) error {
return errorx.NewBiz("您无权删除该标签")
}
if p.CountByCond(&entity.TagTree{Pid: id}) > 0 {
return errorx.NewBiz("请先移除该标签关联的资源")
}
return p.Tx(ctx, func(ctx context.Context) error {
return p.DeleteById(ctx, id)
return p.DeleteTagByParam(ctx, &DelResourceTagParam{
Id: id,
})
}, func(ctx context.Context) error {
// 删除该标签关联的团队信息
return p.tagTreeTeamRepo.DeleteByCond(ctx, &entity.TagTreeTeam{TagId: id})
})
}
func (p *tagTreeAppImpl) toTags(parentTags []*entity.TagTree, param *ResourceTag) []*entity.TagTree {
tags := make([]*entity.TagTree, 0)
// 递归函数,将标签及其子标签展开为一个扁平数组
var flattenTags func(parentTag *entity.TagTree, tag *ResourceTag)
flattenTags = func(parentTag *entity.TagTree, resourceTagParam *ResourceTag) {
if resourceTagParam == nil {
return
}
tagType := resourceTagParam.Type
tagCode := resourceTagParam.Code
tagName := resourceTagParam.Name
if tagName == "" {
tagName = tagCode
}
tag := &entity.TagTree{
Code: tagCode,
CodePath: fmt.Sprintf("%s%d%s%s%s", parentTag.CodePath, tagType, entity.CodePathResourceSeparator, tagCode, entity.CodePathSeparator), // tag1/tag2/1|resourceCode1/11|resourceCode2/
Type: tagType,
Name: tagName,
}
// 将当前标签加入数组
tags = append(tags, tag)
// 递归处理子标签
for _, child := range resourceTagParam.Children {
flattenTags(tag, child)
}
}
for _, parentTag := range parentTags {
// 开始展开标签
flattenTags(parentTag, param)
}
return tags
}

View File

@@ -5,7 +5,6 @@ import "mayfly-go/pkg/model"
type TagTreeQuery struct {
model.Model
Pid uint64
Type TagType `json:"type"`
Code string `json:"code"` // 标识
Codes []string

View File

@@ -97,16 +97,6 @@ func (m *ResourceAuthCert) GetExtraString(key string) string {
return cast.ToString(m.Extra[key])
}
// IResourceTag接口 授权凭证名 -> 资源标签code
func (m *ResourceAuthCert) GetCode() string {
return m.Name
}
// IResourceTag接口 授权凭证用户名 -> 资源标签名
func (m *ResourceAuthCert) GetName() string {
return m.Username
}
// HasChanged 与指定授权凭证比较是否有变更
func (m *ResourceAuthCert) HasChanged(rac *ResourceAuthCert) bool {
if rac == nil {

View File

@@ -1,19 +1,22 @@
package entity
import (
"fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"strings"
"github.com/may-fly/cast"
)
// 标签树
type TagTree struct {
model.Model
Pid uint64 `json:"pid"`
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
Code string `json:"code"` // 标识编码, 若类型不为-1则为对应资源编码
CodePath string `json:"codePath"` // 标识路径
CodePath string `json:"codePath"` // 标识路径tag1/tag2/tagType1|tagCode/tagType2|yyycode/,非普通标签类型段含有标签类型
Name string `json:"name"` // 名称
Remark string `json:"remark"` // 备注说明
}
@@ -28,18 +31,22 @@ const (
TagTypeTag TagType = -1
TagTypeMachine TagType = TagType(consts.ResourceTypeMachine)
TagTypeDb TagType = TagType(consts.ResourceTypeDb)
TagTypeDb TagType = TagType(consts.ResourceTypeDb) // 数据库实例
TagTypeRedis TagType = TagType(consts.ResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.ResourceTypeMongo)
// ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证)
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
TagTypeDbAuthCert TagType = 21 // 数据库-授权凭证
TagTypeDbName TagType = 22 // 数据库名
)
// GetRootCode 获取根路径信息
func (pt *TagTree) GetRootCode() string {
return strings.Split(pt.CodePath, CodePathSeparator)[0]
func (pt *TagTree) IsRoot() bool {
// 去除路径两端可能存在的斜杠
path := strings.Trim(pt.CodePath, "/")
return len(strings.Split(path, "/")) == 1
}
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> index = 0 => test/test1/ index = 1 => test/
@@ -98,13 +105,11 @@ type ITagResource interface {
// 资源关联的标签信息
type ResourceTag struct {
TagId uint64 `json:"tagId" gorm:"-"`
TagPath string `json:"tagPath" gorm:"-"` // 标签路径
CodePath string `json:"codePath" gorm:"-"` // 标签路径
}
func (r *ResourceTag) SetTagInfo(rt ResourceTag) {
r.TagId = rt.TagId
r.TagPath = rt.TagPath
r.CodePath = rt.CodePath
}
// 资源标签列表
@@ -118,3 +123,66 @@ func (r *ResourceTags) SetTagInfo(rt ResourceTag) {
}
r.Tags = append(r.Tags, rt)
}
// GetCodeByPath 从codePaths中提取指定标签类型的所有tagCode并去重
// 如codePaths = tag1/tag2/1|xxxcode/11|yyycode/, tagType = 1 -> xxxcode, tagType = 11 -> yyycode
func GetCodeByPath(tagType TagType, codePaths ...string) []string {
var codes []string
for _, codePath := range codePaths {
// tag1/tag2/1|xxxcode/11|yyycode根据 /tagType|resourceCode进行切割
splStrs := strings.Split(codePath, fmt.Sprintf("%s%d%s", CodePathSeparator, tagType, CodePathResourceSeparator))
if len(splStrs) < 2 {
continue
}
codes = append(codes, strings.Split(splStrs[1], CodePathSeparator)[0])
}
return collx.ArrayDeduplicate[string](codes)
}
// TagPathSection 标签路径段
type TagPathSection struct {
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
Code string `json:"code"` // 标识编码, 若类型不为-1则为对应资源编码
}
type TagPathSections []*TagPathSection // 标签段数组
// 转为codePath
func (tps TagPathSections) ToCodePath() string {
return strings.Join(collx.ArrayMap(tps, func(tp *TagPathSection) string {
if tp.Type == TagTypeTag {
return tp.Code
}
return fmt.Sprintf("%d%s%s", tp.Type, CodePathResourceSeparator, tp.Code)
}), CodePathSeparator) + CodePathSeparator
}
// GetTagPathSections 根据标签路径获取路径段落
func GetTagPathSections(codePath string) TagPathSections {
codePath = strings.TrimSuffix(codePath, CodePathSeparator)
var sections TagPathSections
codes := strings.Split(codePath, CodePathSeparator)
for _, code := range codes {
typeAndCode := strings.Split(code, CodePathResourceSeparator)
var tagType TagType
var tagCode string
if len(typeAndCode) < 2 {
tagType = TagTypeTag
tagCode = typeAndCode[0]
} else {
tagType = TagType(cast.ToInt(typeAndCode[0]))
tagCode = typeAndCode[1]
}
sections = append(sections, &TagPathSection{
Type: tagType,
Code: tagCode,
})
}
return sections
}

View File

@@ -16,7 +16,7 @@ func newTagTreeRepo() repository.TagTree {
}
func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEntity any, orderBy ...string) {
sql := "SELECT DISTINCT(p.id), p.pid, p.type, p.code, p.code_path, p.name, p.remark, p.create_time, p.creator, p.update_time, p.modifier FROM t_tag_tree p WHERE p.is_deleted = 0 "
sql := "SELECT DISTINCT(p.id), p.type, p.code, p.code_path, p.name, p.remark, p.create_time, p.creator, p.update_time, p.modifier FROM t_tag_tree p WHERE p.is_deleted = 0 "
params := make([]any, 0)
if condition.Name != "" {
@@ -39,10 +39,6 @@ func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEn
sql = sql + " AND p.code_path LIKE ?"
params = append(params, condition.CodePathLike+"%")
}
if condition.Pid != 0 {
sql = sql + " AND p.pid = ?"
params = append(params, condition.Pid)
}
if condition.Type != 0 {
sql = sql + " AND p.type = ?"
params = append(params, condition.Type)

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.8.0"
Version = "v1.8.1"
)
func GetAppInfo() string {

View File

@@ -0,0 +1,145 @@
-- 关联数据库实例至标签
INSERT
INTO
t_tag_tree (code_path,
code,
type,
NAME,
create_time,
creator_id,
creator,
update_time,
modifier_id,
modifier,
is_deleted )
SELECT
DISTINCT t1.newCodePath,
t1.tagCode,
100,
t1.tagName,
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
0
FROM
(
SELECT
ttt.code_path,
ttt2.code_path as p_code_path,
td.name,
td.auth_cert_name,
tdi.code tagCode,
tdi.name tagName,
CONCAT(ttt2.code_path, '2|', tdi.code, '/') newCodePath
FROM
t_tag_tree ttt
JOIN t_db td ON
ttt.code = td.code
AND ttt.type = 2
AND td.is_deleted = 0
JOIN t_tag_tree ttt2 ON
ttt.pid = ttt2.id
JOIN t_db_instance tdi ON
tdi.id = td.instance_id) AS t1;
-- 关联数据库实例的授权凭证信息至标签
INSERT
INTO
t_tag_tree (code_path,
code,
type,
NAME,
create_time,
creator_id,
creator,
update_time,
modifier_id,
modifier,
is_deleted )
SELECT
DISTINCT t1.newCodePath,
t1.tagCode,
21,
t1.tagName,
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
0
FROM
(
SELECT
ttt.code_path,
ttt2.code_path as p_code_path,
td.name,
td.auth_cert_name,
trac.name tagCode,
trac.username tagName,
CONCAT(ttt2.code_path, '2|', tdi.code, '/21|', trac.name, '/') as newCodePath
FROM
t_tag_tree ttt
JOIN t_db td ON
ttt.code = td.code
AND ttt.type = 2
AND td.is_deleted = 0
JOIN t_tag_tree ttt2 ON
ttt.pid = ttt2.id
JOIN t_db_instance tdi ON
tdi.id = td.instance_id
JOIN t_resource_auth_cert trac ON
trac.name = td.auth_cert_name
AND trac.is_deleted = 0) AS t1;
-- 关联数据库至标签
INSERT
INTO
t_tag_tree (code_path,
code,
type,
NAME,
create_time,
creator_id,
creator,
update_time,
modifier_id,
modifier,
is_deleted )
SELECT
DISTINCT t1.newCodePath,
t1.tagCode,
22,
t1.name,
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
0
FROM
(
SELECT
ttt.code_path,
ttt.code_path as p_code_path,
td.name ,
td.auth_cert_name,
td.code tagCode,
td.name tagName,
CONCAT(ttt.code_path, '22|', td.code, '/') newCodePath
FROM
t_tag_tree ttt
join t_db td on
ttt.code = td.auth_cert_name
and ttt.type = 21
and td.is_deleted = 0) as t1;
UPDATE t_tag_tree SET is_deleted = 0, delete_time = NOW() WHERE `type` = 2;
UPDATE t_tag_tree SET `type` = 2 WHERE `type` = 100;
ALTER TABLE t_tag_tree DROP COLUMN pid;