refactor: merge tenant configuration into tenant registry

This commit is contained in:
richjohnrambo
2026-09-22 16:47:40 +08:00
parent 851eca97ed
commit 85b564e2ac
22 changed files with 216 additions and 634 deletions
@@ -40,10 +40,9 @@ func (s *sSysImMessageEncryption) List(ctx context.Context, in *sysin.ImMessageE
}
db := imtenant.RegistryDB()
mod := db.Model(dao.ImTenant.Table()+" t").Safe().Ctx(ctx).
LeftJoin(dao.ImTenantConfig.Table()+" c", "c.tenant_id=t.id").
Fields(
"t.id tenant_id", "t.code tenant_code", "t.name tenant_name",
"COALESCE(c."+imTenantMessageEncryptionColumn+",0) message_encryption_enabled",
"t."+imTenantMessageEncryptionColumn+" message_encryption_enabled",
"COALESCE((SELECT MAX(k.key_version) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id AND k.status='active'),0) active_version",
"COALESCE((SELECT MAX(k.key_version) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id AND k.status='pending'),0) pending_version",
"(SELECT COUNT(1) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id) key_count",
@@ -56,7 +55,7 @@ func (s *sSysImMessageEncryption) List(ctx context.Context, in *sysin.ImMessageE
mod = mod.Where("t.code LIKE ? OR t.name LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
}
if in.Enabled != nil {
mod = mod.Where("COALESCE(c."+imTenantMessageEncryptionColumn+",0)=?", *in.Enabled)
mod = mod.Where("t."+imTenantMessageEncryptionColumn, *in.Enabled)
}
if err = mod.Page(in.Page, in.PerPage).OrderAsc("t.id").ScanAndCount(&list, &total, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取商户消息加密配置失败")
@@ -185,16 +184,16 @@ func (s *sSysImMessageEncryption) Switch(ctx context.Context, in *sysin.ImMessag
return gerror.New("启用消息加密前必须先初始化有效密钥")
}
}
result, err := imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).
Data(g.Map{imTenantMessageEncryptionColumn: in.Enabled, dao.ImTenantConfig.Columns().UpdatedBy: contexts.GetUserId(ctx)}).Update()
result, err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
Where(dao.ImTenant.Columns().Id, tenant.Id).
Data(g.Map{dao.ImTenant.Columns().MessageEncryptionEnabled: in.Enabled, dao.ImTenant.Columns().UpdatedBy: contexts.GetUserId(ctx)}).Update()
if err != nil {
return gerror.Wrap(err, "更新商户消息加密开关失败")
}
if affected, _ := result.RowsAffected(); affected == 0 {
return gerror.New("商户配置不存在")
}
_ = writeImAdminAudit(ctx, tenant.Id, "message_encryption_switch", "im_tenant_config", tenant.Id, "success", g.Map{"enabled": in.Enabled})
_ = writeImAdminAudit(ctx, tenant.Id, "message_encryption_switch", dao.ImTenant.Table(), tenant.Id, "success", g.Map{"enabled": in.Enabled})
return publishImPolicyEvent(ctx, tenant.Id, tenant.Code, "tenant.features.updated", "message_encryption", 0)
}
@@ -203,8 +202,8 @@ func (s *sSysImMessageEncryption) GetKey(ctx context.Context, in *sysin.ImMessag
if err != nil {
return nil, err
}
enabledValue, err := imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Fields(imTenantMessageEncryptionColumn).Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).Value()
enabledValue, err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
Fields(dao.ImTenant.Columns().MessageEncryptionEnabled).Where(dao.ImTenant.Columns().Id, tenant.Id).Value()
if err != nil {
return nil, gerror.Wrap(err, "获取商户消息加密开关失败")
}
@@ -207,8 +207,8 @@ func (s *sSysImSecurityPolicy) Publish(ctx context.Context, in *sysin.ImSecurity
return updateErr
}
if tenantID > 0 {
tc := dao.ImTenantConfig.Columns()
if _, updateErr := tx.Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).Where(tc.TenantId, tenantID).Data(g.Map{tc.SecurityPolicyVersion: row[c.PolicyVersion].Uint(), tc.GlobalFriendEnabled: row[c.GlobalFriendEnabled].Int(), tc.MessageCurfewEnabled: row[c.MessageCurfewEnabled].Int(), tc.UpdatedAt: now, tc.UpdatedBy: operator}).Update(); updateErr != nil {
tc := dao.ImTenant.Columns()
if _, updateErr := tx.Model(dao.ImTenant.Table()).Safe().Ctx(ctx).Where(tc.Id, tenantID).Data(g.Map{tc.SecurityPolicyVersion: row[c.PolicyVersion].Uint(), tc.GlobalFriendEnabled: row[c.GlobalFriendEnabled].Int(), tc.MessageCurfewEnabled: row[c.MessageCurfewEnabled].Int(), tc.UpdatedAt: now, tc.UpdatedBy: operator}).Update(); updateErr != nil {
return updateErr
}
}
+33 -204
View File
@@ -27,28 +27,9 @@ import (
type sSysImTenant struct{}
const (
tenantConfigAliyunModerationEnabled = "aliyun_moderation_enabled"
tenantConfigLocalModerationEnabled = "local_moderation_enabled"
tenantConfigMessageEncryptionEnabled = "message_encryption_enabled"
tenantConfigRealnameReviewPolicy = "realname_review_policy"
)
// imManageRoleKey 商户级IM管理员角色标识
const imManageRoleKey = "imManage"
type imTenantConfigModeration struct {
Id int64 `json:"id" orm:"id"`
TenantId int64 `json:"tenant_id" orm:"tenant_id"`
MaxUsers uint64 `json:"max_users" orm:"max_users"`
MaxGroups uint64 `json:"max_groups" orm:"max_groups"`
MaxGroupMembers uint `json:"max_group_members" orm:"max_group_members"`
AliyunModerationEnabled int `json:"aliyun_moderation_enabled" orm:"aliyun_moderation_enabled"`
LocalModerationEnabled int `json:"local_moderation_enabled" orm:"local_moderation_enabled"`
MessageEncryptionEnabled int `json:"message_encryption_enabled" orm:"message_encryption_enabled"`
RealnameReviewPolicy int `json:"realname_review_policy" orm:"realname_review_policy"`
}
func NewSysImTenant() *sSysImTenant {
return &sSysImTenant{}
}
@@ -62,10 +43,6 @@ func (s *sSysImTenant) Model(ctx context.Context, option ...*handler.Option) *gd
return handler.Model(imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx), option...)
}
func (s *sSysImTenant) configModel(ctx context.Context) *gdb.Model {
return imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx)
}
// List 获取IM租户列表
func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (list []*sysin.ImTenantListModel, totalCount int, err error) {
if err = in.Filter(ctx); err != nil {
@@ -77,7 +54,13 @@ func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (lis
dao.ImTenant.Columns().Code,
dao.ImTenant.Columns().Name,
dao.ImTenant.Columns().TenantDesc,
dao.ImTenant.Columns().DbName,
dao.ImTenant.Columns().MaxUsers,
dao.ImTenant.Columns().MaxGroups,
dao.ImTenant.Columns().MaxGroupMembers,
dao.ImTenant.Columns().AliyunModerationEnabled,
dao.ImTenant.Columns().LocalModerationEnabled,
dao.ImTenant.Columns().MessageEncryptionEnabled,
dao.ImTenant.Columns().RealnameReviewPolicy,
dao.ImTenant.Columns().State,
dao.ImTenant.Columns().CreatedBy,
dao.ImTenant.Columns().UpdatedBy,
@@ -102,9 +85,6 @@ func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (lis
if in.Name != "" {
mod = mod.WhereLike(dao.ImTenant.Columns().Name, "%"+in.Name+"%")
}
if in.DbName != "" && isSuperTenantAdmin(ctx) {
mod = mod.WhereLike(dao.ImTenant.Columns().DbName, "%"+in.DbName+"%")
}
if in.State != nil {
mod = mod.Where(dao.ImTenant.Columns().State, *in.State)
}
@@ -120,64 +100,6 @@ func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (lis
err = gerror.Wrap(err, "获取IM租户列表失败,请稍后重试!")
return
}
if len(list) == 0 {
return
}
if err = s.fillListConfig(ctx, list); err != nil {
return
}
// 业务库名仅超管可见,非超管脱敏返回
if !isSuperTenantAdmin(ctx) {
for _, item := range list {
item.DbName = ""
}
}
return
}
func (s *sSysImTenant) fillListConfig(ctx context.Context, list []*sysin.ImTenantListModel) (err error) {
ids := make([]int64, 0, len(list))
for _, item := range list {
ids = append(ids, item.Id)
}
var configs []*imTenantConfigModeration
if err = s.configModel(ctx).
WhereIn(dao.ImTenantConfig.Columns().TenantId, ids).
Scan(&configs); err != nil {
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
}
configMap := make(map[int64]*imTenantConfigModeration, len(configs))
for _, item := range configs {
configMap[item.TenantId] = item
}
var tenants []*entity.ImTenant
if err = s.Model(ctx).
Fields(dao.ImTenant.Columns().Id, dao.ImTenant.Columns().DbDsn).
WhereIn(dao.ImTenant.Columns().Id, ids).
Scan(&tenants); err != nil {
return gerror.Wrap(err, "获取IM租户连接配置失败,请稍后重试!")
}
dsnMap := make(map[int64]string, len(tenants))
for _, item := range tenants {
dsnMap[item.Id] = item.DbDsn
}
super := isSuperTenantAdmin(ctx)
for _, item := range list {
if conf, ok := configMap[item.Id]; ok {
item.MaxUsers = conf.MaxUsers
item.MaxGroups = conf.MaxGroups
item.MaxGroupMembers = conf.MaxGroupMembers
item.AliyunModerationEnabled = conf.AliyunModerationEnabled
item.LocalModerationEnabled = conf.LocalModerationEnabled
item.MessageEncryptionEnabled = conf.MessageEncryptionEnabled
item.RealnameReviewPolicy = conf.RealnameReviewPolicy
}
item.DbDsnConfigured = super && dsnMap[item.Id] != ""
}
return
}
@@ -230,14 +152,10 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
if err = ensureTenantIDSequence(ctx); err != nil {
return err
}
if in.DbName, in.DbDsn, err = sharedDatabaseConfig(ctx); err != nil {
return err
}
in.State = consts.ImTenantStateProvisioning
} else if !super {
// 业务库名与独立连接串仅超管可维护,非超管提交一律强制忽略。
in.DbName = ""
in.DbDsn = ""
}
if err = validateTenantMessageEncryption(ctx, in.Id, in.MessageEncryptionEnabled); err != nil {
return err
}
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
@@ -248,21 +166,24 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
if in.Id > 0 {
in.UpdatedBy = userId
data := g.Map{
dao.ImTenant.Columns().Code: in.Code,
dao.ImTenant.Columns().Name: in.Name,
dao.ImTenant.Columns().TenantDesc: in.TenantDesc,
dao.ImTenant.Columns().State: in.State,
dao.ImTenant.Columns().UpdatedBy: userId,
}
if super {
data[dao.ImTenant.Columns().DbName] = in.DbName
data[dao.ImTenant.Columns().DbDsn] = in.DbDsn
dao.ImTenant.Columns().Code: in.Code,
dao.ImTenant.Columns().Name: in.Name,
dao.ImTenant.Columns().TenantDesc: in.TenantDesc,
dao.ImTenant.Columns().MaxUsers: in.MaxUsers,
dao.ImTenant.Columns().MaxGroups: in.MaxGroups,
dao.ImTenant.Columns().MaxGroupMembers: in.MaxGroupMembers,
dao.ImTenant.Columns().AliyunModerationEnabled: in.AliyunModerationEnabled,
dao.ImTenant.Columns().LocalModerationEnabled: in.LocalModerationEnabled,
dao.ImTenant.Columns().MessageEncryptionEnabled: in.MessageEncryptionEnabled,
dao.ImTenant.Columns().RealnameReviewPolicy: in.RealnameReviewPolicy,
dao.ImTenant.Columns().State: in.State,
dao.ImTenant.Columns().UpdatedBy: userId,
}
if _, err = s.Model(ctx).
WherePri(in.Id).Data(data).Update(); err != nil {
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
}
return s.saveConfig(ctx, in, userId)
return nil
}
in.Id, err = nextTenantID(tx)
@@ -278,7 +199,7 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
}
tenantID = in.Id
return s.saveConfig(ctx, in, userId)
return nil
})
if err != nil {
return err
@@ -413,15 +334,7 @@ func (s *sSysImTenant) resetTenantAdminPassword(ctx context.Context, tenantId in
// rollbackTenantCreation 商户创建补偿:删除已写入的商户及配置记录
func (s *sSysImTenant) rollbackTenantCreation(ctx context.Context, tenantId int64) {
_ = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
WherePri(tenantId).Delete(); err != nil {
return
}
_, err = s.configModel(ctx).
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).Delete()
return
})
_, _ = s.Model(ctx, &handler.Option{FilterAuth: false}).WherePri(tenantId).Delete()
}
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
@@ -439,10 +352,10 @@ func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code strin
return
}
func (s *sSysImTenant) saveConfig(ctx context.Context, in *sysin.ImTenantEditInp, userId int64) (err error) {
if in.MessageEncryptionEnabled == 1 {
func validateTenantMessageEncryption(ctx context.Context, tenantId int64, enabled int) error {
if enabled == 1 {
count, queryErr := imtenant.RegistryDB().Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", in.Id).
Where("tenant_id", tenantId).
Where("status", consts.ImMessageKeyStatusActive).
Count()
if queryErr != nil {
@@ -452,61 +365,7 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, in *sysin.ImTenantEditInp
return gerror.New("启用消息加密前必须先在消息密钥管理中初始化有效密钥")
}
}
var conf *entity.ImTenantConfig
if err = s.configModel(ctx).
Where(dao.ImTenantConfig.Columns().TenantId, in.Id).
Scan(&conf); err != nil {
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
}
data := g.Map{
dao.ImTenantConfig.Columns().MaxUsers: in.MaxUsers,
dao.ImTenantConfig.Columns().MaxGroups: in.MaxGroups,
dao.ImTenantConfig.Columns().MaxGroupMembers: in.MaxGroupMembers,
tenantConfigAliyunModerationEnabled: in.AliyunModerationEnabled,
tenantConfigLocalModerationEnabled: in.LocalModerationEnabled,
tenantConfigMessageEncryptionEnabled: in.MessageEncryptionEnabled,
tenantConfigRealnameReviewPolicy: in.RealnameReviewPolicy,
}
if conf != nil && conf.Id > 0 {
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
if _, err = s.configModel(ctx).
Fields(
dao.ImTenantConfig.Columns().MaxUsers,
dao.ImTenantConfig.Columns().MaxGroups,
dao.ImTenantConfig.Columns().MaxGroupMembers,
tenantConfigAliyunModerationEnabled,
tenantConfigLocalModerationEnabled,
tenantConfigMessageEncryptionEnabled,
tenantConfigRealnameReviewPolicy,
dao.ImTenantConfig.Columns().UpdatedBy,
).
WherePri(conf.Id).Data(data).Update(); err != nil {
return gerror.Wrap(err, "修改IM租户配置失败,请稍后重试!")
}
return
}
data[dao.ImTenantConfig.Columns().TenantId] = in.Id
data[dao.ImTenantConfig.Columns().CreatedBy] = userId
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
if _, err = s.configModel(ctx).
Fields(
dao.ImTenantConfig.Columns().TenantId,
dao.ImTenantConfig.Columns().MaxUsers,
dao.ImTenantConfig.Columns().MaxGroups,
dao.ImTenantConfig.Columns().MaxGroupMembers,
tenantConfigAliyunModerationEnabled,
tenantConfigLocalModerationEnabled,
tenantConfigMessageEncryptionEnabled,
tenantConfigRealnameReviewPolicy,
dao.ImTenantConfig.Columns().CreatedBy,
dao.ImTenantConfig.Columns().UpdatedBy,
).
Data(data).Insert(); err != nil {
return gerror.Wrap(err, "新增IM租户配置失败,请稍后重试!")
}
return
return nil
}
// View 获取IM租户指定信息
@@ -522,23 +381,6 @@ func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res
return
}
var conf *imTenantConfigModeration
if err = s.configModel(ctx).
Where(dao.ImTenantConfig.Columns().TenantId, in.Id).
Scan(&conf); err != nil {
err = gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
return
}
if conf != nil {
res.MaxUsers = conf.MaxUsers
res.MaxGroups = conf.MaxGroups
res.MaxGroupMembers = conf.MaxGroupMembers
res.AliyunModerationEnabled = conf.AliyunModerationEnabled
res.LocalModerationEnabled = conf.LocalModerationEnabled
res.MessageEncryptionEnabled = conf.MessageEncryptionEnabled
res.RealnameReviewPolicy = conf.RealnameReviewPolicy
}
res.DbDsnConfigured = res.DbDsn != ""
// 查询商户自动创建的管理员账号(编辑弹窗展示)
roleVal, roleErr := dao.AdminRole.Ctx(ctx).
Fields(dao.AdminRole.Columns().Id).
@@ -558,12 +400,6 @@ func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res
res.AdminUsername = userVal.String()
}
}
// 业务库名与独立连接串仅超管可见,非超管全部脱敏
if !isSuperTenantAdmin(ctx) {
res.DbName = ""
res.DbDsn = ""
res.DbDsnConfigured = false
}
return
}
@@ -601,17 +437,10 @@ func (s *sSysImTenant) Usage(ctx context.Context, in *sysin.ImTenantUsageInp) (r
}
res = &sysin.ImTenantUsageModel{
Id: tenant.Id,
Code: tenant.Code,
Name: tenant.Name,
DbName: tenant.DbName,
DbDsnConfigured: tenant.DbDsn != "",
Warnings: []string{},
}
// 业务库名与连接串信息仅超管可见,非超管全部脱敏
if !isSuperTenantAdmin(ctx) {
res.DbName = ""
res.DbDsnConfigured = false
Id: tenant.Id,
Code: tenant.Code,
Name: tenant.Name,
Warnings: []string{},
}
db, _, dbErr := imtenant.DB(ctx, tenant.Id)
@@ -12,17 +12,6 @@ import (
const imTenantIDSequenceTable = "im_tenant_id_sequence"
func sharedDatabaseConfig(ctx context.Context) (dbName, dbDSN string, err error) {
row, err := imtenant.RegistryDB().GetOne(ctx, "SELECT db_name,db_dsn FROM im_tenant WHERE code='default' LIMIT 1")
if err != nil {
return "", "", gerror.Wrap(err, "获取共享业务库配置失败")
}
if row.IsEmpty() {
return "", "", gerror.New("默认商户不存在,无法确定共享业务库")
}
return row["db_name"].String(), row["db_dsn"].String(), nil
}
func ensureTenantIDSequence(ctx context.Context) error {
db := imtenant.RegistryDB()
if _, err := db.Exec(ctx, `CREATE TABLE IF NOT EXISTS im_tenant_id_sequence (