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
@@ -35,8 +35,6 @@ type TenantModel struct {
Code string `json:"code"`
Name string `json:"name"`
TenantDesc *string `json:"tenant_desc,omitempty"`
DBName string `json:"db_name"`
DBDSN string `json:"db_dsn"`
State int `json:"state"`
CreatedAt string `json:"created_at"`
CreatedBy int64 `json:"created_by"`
@@ -178,17 +178,8 @@ func (c *cConfig) ModerationPolicy(ctx context.Context, req *api.ModerationPolic
return nil, err
}
res = defaultModerationPolicy(tenant)
var tenantConfig struct {
AliyunModerationEnabled int `json:"aliyun_moderation_enabled"`
LocalModerationEnabled int `json:"local_moderation_enabled"`
}
if err = imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Fields("aliyun_moderation_enabled", "local_moderation_enabled").
Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).Scan(&tenantConfig); err != nil {
return nil, gerror.Wrap(err, "获取商户内容审核开关失败")
}
res.AliyunEnabled = tenantConfig.AliyunModerationEnabled == 1
res.LocalWordsEnabled = tenantConfig.LocalModerationEnabled == 1
res.AliyunEnabled = tenant.AliyunModerationEnabled == 1
res.LocalWordsEnabled = tenant.LocalModerationEnabled == 1
res.Enabled = res.AliyunEnabled || res.LocalWordsEnabled
columns := dao.ImSecurityPolicy.Columns()
var row struct {
@@ -333,18 +324,7 @@ func (c *cConfig) RealnamePolicy(ctx context.Context, req *api.RealnamePolicyReq
return nil, err
}
res = defaultRealnamePolicy(tenant.Id)
var row *struct {
RealnameReviewPolicy int `json:"realname_review_policy"`
}
if err = imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Fields("realname_review_policy").
Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).Scan(&row); err != nil {
return nil, gerror.Wrap(err, "获取租户实名认证策略失败")
}
if row == nil {
return res, nil
}
if row.RealnameReviewPolicy == 1 {
if tenant.RealnameReviewPolicy == 1 {
res.RealnameReviewPolicy = 1
}
return res, nil
@@ -553,8 +533,6 @@ func tenantToModel(tenant *entity.ImTenant) *api.TenantModel {
Code: tenant.Code,
Name: tenant.Name,
TenantDesc: &desc,
DBName: tenant.DbName,
DBDSN: tenant.DbDsn,
State: tenant.State,
CreatedAt: gtimeToRFC3339(tenant.CreatedAt),
CreatedBy: tenant.CreatedBy,
-22
View File
@@ -1,22 +0,0 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"hotgo/internal/dao/internal"
)
// imTenantConfigDao is the data access object for the table im_tenant_config.
// You can define custom methods on it to extend its functionality as needed.
type imTenantConfigDao struct {
*internal.ImTenantConfigDao
}
var (
// ImTenantConfig is a globally accessible object for table im_tenant_config operations.
ImTenantConfig = imTenantConfigDao{internal.NewImTenantConfigDao()}
)
// Add your custom methods and functionality below.
+48 -22
View File
@@ -21,32 +21,58 @@ type ImTenantDao struct {
// ImTenantColumns defines and stores column names for the table im_tenant.
type ImTenantColumns struct {
Id string //
Code string //
Name string //
TenantDesc string //
DbName string //
DbDsn string //
State string //
CreatedAt string //
CreatedBy string //
UpdatedAt string //
UpdatedBy string //
Id string //
Code string //
Name string //
TenantDesc string //
MaxUsers string // 0表示不限制
MaxGroups string // 0表示不限制
MaxGroupMembers string // 0表示不限制
RealnameReviewPolicy string // 实名审核策略:0自动通过,1转人工审核
RealnameProviderMode string // 实名认证模式
RealnameRequired string // 是否强制实名
RealnameAllowSkip string // 是否允许跳过实名
RealnameRemindAfter string // 实名提醒延迟
AliyunModerationEnabled string // 是否启用阿里云内容审核
LocalModerationEnabled string // 是否启用本地内容审核
MessageEncryptionEnabled string // 是否启用消息加密
DefaultPolicyGroupId string // 默认用户策略组
SecurityPolicyVersion string // 已发布安全策略版本
GlobalFriendEnabled string // 全局加好友开关
MessageCurfewEnabled string // 消息宵禁开关
State string //
CreatedAt string //
CreatedBy string //
UpdatedAt string //
UpdatedBy string //
}
// imTenantColumns holds the columns for the table im_tenant.
var imTenantColumns = ImTenantColumns{
Id: "id",
Code: "code",
Name: "name",
TenantDesc: "tenant_desc",
DbName: "db_name",
DbDsn: "db_dsn",
State: "state",
CreatedAt: "created_at",
CreatedBy: "created_by",
UpdatedAt: "updated_at",
UpdatedBy: "updated_by",
Id: "id",
Code: "code",
Name: "name",
TenantDesc: "tenant_desc",
MaxUsers: "max_users",
MaxGroups: "max_groups",
MaxGroupMembers: "max_group_members",
RealnameReviewPolicy: "realname_review_policy",
RealnameProviderMode: "realname_provider_mode",
RealnameRequired: "realname_required",
RealnameAllowSkip: "realname_allow_skip",
RealnameRemindAfter: "realname_remind_after",
AliyunModerationEnabled: "aliyun_moderation_enabled",
LocalModerationEnabled: "local_moderation_enabled",
MessageEncryptionEnabled: "message_encryption_enabled",
DefaultPolicyGroupId: "default_policy_group_id",
SecurityPolicyVersion: "security_policy_version",
GlobalFriendEnabled: "global_friend_enabled",
MessageCurfewEnabled: "message_curfew_enabled",
State: "state",
CreatedAt: "created_at",
CreatedBy: "created_by",
UpdatedAt: "updated_at",
UpdatedBy: "updated_by",
}
// NewImTenantDao creates and returns a new DAO object for table data access.
@@ -1,103 +0,0 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// ImTenantConfigDao is the data access object for the table im_tenant_config.
type ImTenantConfigDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns ImTenantConfigColumns // columns contains all the column names of Table for convenient usage.
handlers []gdb.ModelHandler // handlers for customized model modification.
}
// ImTenantConfigColumns defines and stores column names for the table im_tenant_config.
type ImTenantConfigColumns struct {
Id string //
TenantId string //
MaxUsers string // 0 means unlimited
MaxGroups string // 0 means unlimited
MaxGroupMembers string // 0 means unlimited
DefaultPolicyGroupId string // Default user policy group
SecurityPolicyVersion string // Published security policy version
GlobalFriendEnabled string // Published global friend switch
MessageCurfewEnabled string // Published message curfew switch
CreatedAt string //
CreatedBy string //
UpdatedAt string //
UpdatedBy string //
}
// imTenantConfigColumns holds the columns for the table im_tenant_config.
var imTenantConfigColumns = ImTenantConfigColumns{
Id: "id",
TenantId: "tenant_id",
MaxUsers: "max_users",
MaxGroups: "max_groups",
MaxGroupMembers: "max_group_members",
DefaultPolicyGroupId: "default_policy_group_id",
SecurityPolicyVersion: "security_policy_version",
GlobalFriendEnabled: "global_friend_enabled",
MessageCurfewEnabled: "message_curfew_enabled",
CreatedAt: "created_at",
CreatedBy: "created_by",
UpdatedAt: "updated_at",
UpdatedBy: "updated_by",
}
// NewImTenantConfigDao creates and returns a new DAO object for table data access.
func NewImTenantConfigDao(handlers ...gdb.ModelHandler) *ImTenantConfigDao {
return &ImTenantConfigDao{
group: "default",
table: "im_tenant_config",
columns: imTenantConfigColumns,
handlers: handlers,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *ImTenantConfigDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *ImTenantConfigDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *ImTenantConfigDao) Columns() ImTenantConfigColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *ImTenantConfigDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *ImTenantConfigDao) Ctx(ctx context.Context) *gdb.Model {
model := dao.DB().Model(dao.table)
for _, handler := range dao.handlers {
model = handler(model)
}
return model.Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *ImTenantConfigDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
+6 -34
View File
@@ -20,6 +20,7 @@ import (
var tenantDbGroups sync.Map
const registryDbGroup = "imRegistry"
const sharedBusinessDatabase = "meeting_im"
var registryDbGroupOnce sync.Once
@@ -47,19 +48,15 @@ func DB(ctx context.Context, tenantId int64) (db gdb.DB, tenant *entity.ImTenant
// Tenant isolation inside this database is implemented by tenant_id predicates
// and the three tenant-partitioned high-volume table families.
func SharedDB(ctx context.Context) (gdb.DB, error) {
var sharedTenant *entity.ImTenant
if err := RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
Where(dao.ImTenant.Columns().Code, "default").Scan(&sharedTenant); err != nil {
return nil, gerror.Wrap(err, "获取共享业务库配置失败")
defaultLink := defaultDbLink(ctx)
if defaultLink == "" {
return nil, gerror.New("默认数据库连接未配置,无法连接共享业务库")
}
if sharedTenant == nil || sharedTenant.Id <= 0 {
return nil, gerror.New("默认商户不存在,无法确定共享业务库")
}
link, err := tenantLink(ctx, sharedTenant)
link, err := replaceMySQLDatabase(defaultLink, sharedBusinessDatabase)
if err != nil {
return nil, err
}
return dbForLink(link, sharedTenant.Id)
return dbForLink(link, 0)
}
func dbForLink(link string, ownerID int64) (gdb.DB, error) {
@@ -111,31 +108,6 @@ func ensureRegistryDBGroup() {
_ = gdb.SetConfigGroup(registryDbGroup, cloned)
}
func tenantLink(ctx context.Context, tenant *entity.ImTenant) (string, error) {
if tenant.DbDsn != "" {
return normalizeMySQLTimeOptions(tenant.DbDsn)
}
if tenant.DbName == "" {
// 未配置独立业务库的商户回落共享默认库
defaultLink := defaultDbLink(ctx)
if defaultLink == "" {
return "", gerror.New("默认数据库连接未配置,无法回落共享业务库")
}
return normalizeMySQLTimeOptions(defaultLink)
}
defaultLink := defaultDbLink(ctx)
if defaultLink == "" {
return "", gerror.New("默认数据库连接未配置,无法推导租户业务库连接")
}
link, err := replaceMySQLDatabase(defaultLink, tenant.DbName)
if err != nil {
return "", err
}
return link, nil
}
func defaultDbLink(ctx context.Context) string {
if configs, err := gdb.GetConfigGroup("default"); err == nil && len(configs) > 0 {
if configs[0].Link != "" {
@@ -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 (
+25 -12
View File
@@ -11,16 +11,29 @@ import (
// ImTenant is the golang structure of table im_tenant for DAO operations like Where/Data.
type ImTenant struct {
g.Meta `orm:"table:im_tenant, do:true"`
Id any //
Code any //
Name any //
TenantDesc any //
DbName any //
DbDsn any //
State any //
CreatedAt *gtime.Time //
CreatedBy any //
UpdatedAt *gtime.Time //
UpdatedBy any //
g.Meta `orm:"table:im_tenant, do:true"`
Id any //
Code any //
Name any //
TenantDesc any //
MaxUsers any // 0表示不限制
MaxGroups any // 0表示不限制
MaxGroupMembers any // 0表示不限制
RealnameReviewPolicy any // 实名审核策略:0自动通过,1转人工审核
RealnameProviderMode any // 实名认证模式
RealnameRequired any // 是否强制实名
RealnameAllowSkip any // 是否允许跳过实名
RealnameRemindAfter any // 实名提醒延迟
AliyunModerationEnabled any // 是否启用阿里云内容审核
LocalModerationEnabled any // 是否启用本地内容审核
MessageEncryptionEnabled any // 是否启用消息加密
DefaultPolicyGroupId any // 默认用户策略组
SecurityPolicyVersion any // 已发布安全策略版本
GlobalFriendEnabled any // 全局加好友开关
MessageCurfewEnabled any // 消息宵禁开关
State any //
CreatedAt *gtime.Time //
CreatedBy any //
UpdatedAt *gtime.Time //
UpdatedBy any //
}
@@ -1,28 +0,0 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// ImTenantConfig is the golang structure of table im_tenant_config for DAO operations like Where/Data.
type ImTenantConfig struct {
g.Meta `orm:"table:im_tenant_config, do:true"`
Id any //
TenantId any //
MaxUsers any // 0 means unlimited
MaxGroups any // 0 means unlimited
MaxGroupMembers any // 0 means unlimited
DefaultPolicyGroupId any // Default user policy group
SecurityPolicyVersion any // Published security policy version
GlobalFriendEnabled any // Published global friend switch
MessageCurfewEnabled any // Published message curfew switch
CreatedAt *gtime.Time //
CreatedBy any //
UpdatedAt *gtime.Time //
UpdatedBy any //
}
+24 -11
View File
@@ -10,15 +10,28 @@ import (
// ImTenant is the golang structure for table im_tenant.
type ImTenant struct {
Id int64 `json:"id" orm:"id" description:""`
Code string `json:"code" orm:"code" description:""`
Name string `json:"name" orm:"name" description:""`
TenantDesc string `json:"tenantDesc" orm:"tenant_desc" description:""`
DbName string `json:"dbName" orm:"db_name" description:""`
DbDsn string `json:"dbDsn" orm:"db_dsn" description:""`
State int `json:"state" orm:"state" description:""`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:""`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:""`
Id int64 `json:"id" orm:"id" description:""`
Code string `json:"code" orm:"code" description:""`
Name string `json:"name" orm:"name" description:""`
TenantDesc string `json:"tenantDesc" orm:"tenant_desc" description:""`
MaxUsers uint64 `json:"maxUsers" orm:"max_users" description:"0表示不限制"`
MaxGroups uint64 `json:"maxGroups" orm:"max_groups" description:"0表示不限制"`
MaxGroupMembers uint `json:"maxGroupMembers" orm:"max_group_members" description:"0表示不限制"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" orm:"realname_review_policy" description:"实名审核策略:0自动通过,1转人工审核"`
RealnameProviderMode string `json:"realnameProviderMode" orm:"realname_provider_mode" description:"实名认证模式"`
RealnameRequired int `json:"realnameRequired" orm:"realname_required" description:"是否强制实名"`
RealnameAllowSkip int `json:"realnameAllowSkip" orm:"realname_allow_skip" description:"是否允许跳过实名"`
RealnameRemindAfter string `json:"realnameRemindAfter" orm:"realname_remind_after" description:"实名提醒延迟"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" orm:"aliyun_moderation_enabled" description:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" orm:"local_moderation_enabled" description:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" orm:"message_encryption_enabled" description:"是否启用消息加密"`
DefaultPolicyGroupId int64 `json:"defaultPolicyGroupId" orm:"default_policy_group_id" description:"默认用户策略组"`
SecurityPolicyVersion uint `json:"securityPolicyVersion" orm:"security_policy_version" description:"已发布安全策略版本"`
GlobalFriendEnabled int `json:"globalFriendEnabled" orm:"global_friend_enabled" description:"全局加好友开关"`
MessageCurfewEnabled int `json:"messageCurfewEnabled" orm:"message_curfew_enabled" description:"消息宵禁开关"`
State int `json:"state" orm:"state" description:""`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:""`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:""`
}
@@ -1,26 +0,0 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// ImTenantConfig is the golang structure for table im_tenant_config.
type ImTenantConfig struct {
Id int64 `json:"id" orm:"id" description:""`
TenantId int64 `json:"tenantId" orm:"tenant_id" description:""`
MaxUsers uint64 `json:"maxUsers" orm:"max_users" description:"0 means unlimited"`
MaxGroups uint64 `json:"maxGroups" orm:"max_groups" description:"0 means unlimited"`
MaxGroupMembers uint `json:"maxGroupMembers" orm:"max_group_members" description:"0 means unlimited"`
DefaultPolicyGroupId int64 `json:"defaultPolicyGroupId" orm:"default_policy_group_id" description:"Default user policy group"`
SecurityPolicyVersion uint `json:"securityPolicyVersion" orm:"security_policy_version" description:"Published security policy version"`
GlobalFriendEnabled int `json:"globalFriendEnabled" orm:"global_friend_enabled" description:"Published global friend switch"`
MessageCurfewEnabled int `json:"messageCurfewEnabled" orm:"message_curfew_enabled" description:"Published message curfew switch"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:""`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:""`
}
+11 -53
View File
@@ -20,32 +20,10 @@ import (
// ImTenantInsertFields 新增IM租户字段过滤
type ImTenantInsertFields struct {
Id int64 `json:"id" dc:"商户ID"`
Code string `json:"code" dc:"租户编码"`
Name string `json:"name" dc:"租户名称"`
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
DbName string `json:"dbName" dc:"业务库名"`
DbDsn string `json:"dbDsn" dc:"独立连接串"`
State int `json:"state" dc:"状态"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
}
// ImTenantConfigUpdateFields 修改IM租户配置字段过滤
type ImTenantConfigUpdateFields struct {
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
}
// ImTenantConfigInsertFields 新增IM租户配置字段过滤
type ImTenantConfigInsertFields struct {
TenantId int64 `json:"tenantId" dc:"租户ID"`
Id int64 `json:"id" dc:"商户ID"`
Code string `json:"code" dc:"商户编码"`
Name string `json:"name" dc:"商户名称"`
TenantDesc string `json:"tenantDesc" dc:"商户描述"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
@@ -53,21 +31,16 @@ type ImTenantConfigInsertFields struct {
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
State int `json:"state" dc:"状态"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
}
// ImTenantEditInp 修改/新增IM租户
type ImTenantEditInp struct {
entity.ImTenant
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
AdminUsername string `json:"adminUsername" dc:"新增商户时自动创建的后台管理员账号名"`
AdminPassword string `json:"adminPassword" dc:"新增商户时自动创建的后台管理员初始密码"`
AdminUsername string `json:"adminUsername" dc:"新增商户时自动创建的后台管理员账号名"`
AdminPassword string `json:"adminPassword" dc:"新增商户时自动创建的后台管理员初始密码"`
}
func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
@@ -119,19 +92,9 @@ func (in *ImTenantViewInp) Filter(ctx context.Context) (err error) {
type ImTenantViewModel struct {
entity.ImTenant
AdminUsername string `json:"adminUsername" dc:"商户管理员账号"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
TenantDbReachable bool `json:"tenantDbReachable" dc:"业务库是否可达"`
TenantDbCheckError string `json:"tenantDbCheckError" dc:"业务库检查错误"`
AdminUsername string `json:"adminUsername" dc:"商户管理员账号"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
}
// ImTenantListInp 获取IM租户列表
@@ -140,7 +103,6 @@ type ImTenantListInp struct {
Id int64 `json:"id" dc:"ID"`
Code string `json:"code" dc:"租户编码"`
Name string `json:"name" dc:"租户名称"`
DbName string `json:"dbName" dc:"业务库名"`
State *int `json:"state" dc:"状态"`
CreatedAt []*gtime.Time `json:"createdAt" dc:"创建时间"`
}
@@ -154,7 +116,6 @@ type ImTenantListModel struct {
Code string `json:"code" dc:"租户编码"`
Name string `json:"name" dc:"租户名称"`
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
DbName string `json:"dbName" dc:"业务库名"`
State int `json:"state" dc:"状态"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
@@ -163,7 +124,6 @@ type ImTenantListModel struct {
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
@@ -205,8 +165,6 @@ type ImTenantUsageModel struct {
Id int64 `json:"id" dc:"租户ID"`
Code string `json:"code" dc:"租户编码"`
Name string `json:"name" dc:"租户名称"`
DbName string `json:"dbName" dc:"业务库名"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
DbReachable bool `json:"dbReachable" dc:"业务库是否可达"`
DbCheckError string `json:"dbCheckError" dc:"业务库检查错误"`
SchemaVersion string `json:"schemaVersion" dc:"Schema版本"`
@@ -6,9 +6,9 @@ SET @now := NOW();
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config'
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant'
AND COLUMN_NAME = 'message_encryption_enabled') = 0,
'ALTER TABLE `im_tenant_config` ADD COLUMN `message_encryption_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否启用消息加密'' AFTER `local_moderation_enabled`',
'ALTER TABLE `im_tenant` ADD COLUMN `message_encryption_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否启用消息加密'' AFTER `local_moderation_enabled`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
@@ -0,0 +1,38 @@
-- 第一阶段:将商户配置扩展并回填到 im_tenant。
-- 必须先执行本文件,再发布新版 manager 与 IM Server。
-- 本阶段保留 im_tenant_config、db_name、db_dsn,确保旧实例在滚动发布期间仍可运行。
ALTER TABLE `im_tenant`
ADD COLUMN `max_users` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0表示不限制' AFTER `tenant_desc`,
ADD COLUMN `max_groups` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0表示不限制' AFTER `max_users`,
ADD COLUMN `max_group_members` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0表示不限制' AFTER `max_groups`,
ADD COLUMN `realname_review_policy` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '实名审核策略:0自动通过,1转人工审核' AFTER `max_group_members`,
ADD COLUMN `realname_provider_mode` VARCHAR(32) NOT NULL DEFAULT 'id_card_2factor' COMMENT '实名认证模式' AFTER `realname_review_policy`,
ADD COLUMN `realname_required` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否强制实名' AFTER `realname_provider_mode`,
ADD COLUMN `realname_allow_skip` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许跳过实名' AFTER `realname_required`,
ADD COLUMN `realname_remind_after` VARCHAR(32) NOT NULL DEFAULT '24h' COMMENT '实名提醒延迟' AFTER `realname_allow_skip`,
ADD COLUMN `aliyun_moderation_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用阿里云内容审核' AFTER `realname_remind_after`,
ADD COLUMN `local_moderation_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用本地内容审核' AFTER `aliyun_moderation_enabled`,
ADD COLUMN `message_encryption_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用消息加密' AFTER `local_moderation_enabled`,
ADD COLUMN `default_policy_group_id` BIGINT DEFAULT NULL COMMENT '默认用户策略组' AFTER `message_encryption_enabled`,
ADD COLUMN `security_policy_version` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已发布安全策略版本' AFTER `default_policy_group_id`,
ADD COLUMN `global_friend_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '全局加好友开关' AFTER `security_policy_version`,
ADD COLUMN `message_curfew_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '消息宵禁开关' AFTER `global_friend_enabled`;
UPDATE `im_tenant` t
INNER JOIN `im_tenant_config` c ON c.`tenant_id`=t.`id`
SET t.`max_users`=c.`max_users`,
t.`max_groups`=c.`max_groups`,
t.`max_group_members`=c.`max_group_members`,
t.`realname_review_policy`=c.`realname_review_policy`,
t.`realname_provider_mode`=c.`realname_provider_mode`,
t.`realname_required`=c.`realname_required`,
t.`realname_allow_skip`=c.`realname_allow_skip`,
t.`realname_remind_after`=c.`realname_remind_after`,
t.`aliyun_moderation_enabled`=c.`aliyun_moderation_enabled`,
t.`local_moderation_enabled`=c.`local_moderation_enabled`,
t.`message_encryption_enabled`=c.`message_encryption_enabled`,
t.`default_policy_group_id`=c.`default_policy_group_id`,
t.`security_policy_version`=c.`security_policy_version`,
t.`global_friend_enabled`=c.`global_friend_enabled`,
t.`message_curfew_enabled`=c.`message_curfew_enabled`;
@@ -0,0 +1,9 @@
-- 第二阶段:新版 manager 与 IM Server 全部发布并验证后执行。
-- 清理已废弃的商户配置表和按商户选库字段。
DROP TABLE `im_tenant_config`;
ALTER TABLE `im_tenant`
DROP INDEX `idx_im_tenant_db_name`,
DROP COLUMN `db_name`,
DROP COLUMN `db_dsn`;
+1 -15
View File
@@ -42,20 +42,6 @@
/>
</n-form-item>
</n-gi>
<n-gi span="1" v-if="isAdminSuper && formValue.id > 0">
<n-form-item label="业务库名" path="dbName">
<n-input placeholder="请输入业务库名" v-model:value="formValue.dbName" />
</n-form-item>
</n-gi>
<n-gi span="2" v-if="isAdminSuper && formValue.id > 0">
<n-form-item label="独立连接串" path="dbDsn">
<n-input
type="textarea"
placeholder="为空时使用业务库名路由"
v-model:value="formValue.dbDsn"
/>
</n-form-item>
</n-gi>
<n-gi span="1" v-if="formValue.id > 0">
<n-form-item label="管理员账号">
<n-input :value="formValue.adminUsername" disabled placeholder="未创建" />
@@ -171,7 +157,7 @@
import { computed, ref, watch } from 'vue';
import { useDictStore } from '@/store/modules/dict';
import { Add, Edit, View } from '@/api/imTenant';
import { isAdminSuper, newState, rules, State } from './model';
import { newState, rules, State } from './model';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useMessage } from 'naive-ui';
import { adaModalWidth } from '@/utils/hotgo';
+3 -5
View File
@@ -52,7 +52,7 @@
import { useDictStore } from '@/store/modules/dict';
import { List, Restore, Status } from '@/api/imTenant';
import { PlusOutlined } from '@vicons/antd';
import { buildColumns, isAdminSuper, loadOptions, schemas, State } from './model';
import { buildColumns, loadOptions, schemas, State } from './model';
import { adaTableScrollX } from '@/utils/hotgo';
import Edit from './edit.vue';
import UsageModal from './usage.vue';
@@ -116,8 +116,7 @@
},
});
// 业务库名列仅admin超管可见
const columns = computed(() => buildColumns(isAdminSuper.value));
const columns = computed(() => buildColumns());
const scrollX = computed(() => {
return adaTableScrollX(columns.value, actionColumn.width);
@@ -126,8 +125,7 @@
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 90,
// 业务库名搜索项仅admin超管可见
schemas: schemas.value.filter((item) => item.field !== 'dbName' || isAdminSuper.value),
schemas: schemas.value,
});
// 加载表格数据
+3 -44
View File
@@ -1,25 +1,18 @@
import { computed, h, ref } from 'vue';
import { h, ref } from 'vue';
import { NTag } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { defRangeShortcuts } from '@/utils/dateUtil';
import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
import { useDictStore } from '@/store/modules/dict';
import { useUserStore } from '@/store/modules/user';
const dict = useDictStore();
const userStore = useUserStore();
// 是否admin超管(业务库名/独立连接串仅超管可见可编辑)
export const isAdminSuper = computed(() => userStore.getUserInfo?.username === 'admin');
export class State {
public id = 0; // ID
public code = ''; // 商户编码
public name = ''; // 商户名称
public tenantDesc = ''; // 商户描述
public dbName = ''; // 业务库名
public dbDsn = ''; // 独立连接串
public adminUsername = ''; // 新增商户时创建的后台管理员账号
public adminPassword = ''; // 新增商户时创建的后台管理员初始密码
public state = 1; // 状态
@@ -30,7 +23,6 @@ export class State {
public localModerationEnabled = 0; // 是否启用本地内容审核
public messageEncryptionEnabled = 0; // 是否启用消息加密
public realnameReviewPolicy = 0; // 实名审核策略
public dbDsnConfigured = false; // 是否配置独立连接串
public createdBy = 0; // 创建者
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
public updatedBy = 0; // 更新者
@@ -49,8 +41,6 @@ export class UsageState {
public id = 0;
public code = '';
public name = '';
public dbName = '';
public dbDsnConfigured = false;
public dbReachable = false;
public dbCheckError = '';
public schemaVersion = '';
@@ -126,14 +116,6 @@ export const schemas = ref<FormSchema[]>([
placeholder: '请输入商户名称',
},
},
{
field: 'dbName',
component: 'NInput',
label: '业务库名',
componentProps: {
placeholder: '请输入业务库名',
},
},
{
field: 'state',
component: 'NSelect',
@@ -185,25 +167,6 @@ const baseColumns = [
return dict.getLabel('ImTenantStateOptions', row.state);
},
},
{
title: '业务库名',
key: 'dbName',
align: 'left',
width: 160,
},
{
title: '独立连接串',
key: 'dbDsnConfigured',
align: 'left',
width: 120,
render(row: State) {
return h(
NTag,
{ type: row.dbDsnConfigured ? 'success' : 'default', bordered: false },
{ default: () => (row.dbDsnConfigured ? '已配置' : '未配置') }
);
},
},
{
title: '最大用户',
key: 'maxUsers',
@@ -297,12 +260,8 @@ const baseColumns = [
},
];
// 业务库名与独立连接串列仅admin超管可见
export function buildColumns(isAdmin: boolean) {
if (isAdmin) {
return baseColumns;
}
return baseColumns.filter((item) => item.key !== 'dbName' && item.key !== 'dbDsnConfigured');
export function buildColumns() {
return baseColumns;
}
// 加载字典数据选项
-4
View File
@@ -11,10 +11,6 @@
<n-descriptions label-placement="left" bordered :column="settingStore.isMobile ? 1 : 2">
<n-descriptions-item label="商户">{{ usage.name || current.name }}</n-descriptions-item>
<n-descriptions-item label="商户编码">{{ usage.code || current.code }}</n-descriptions-item>
<n-descriptions-item label="业务库">{{ usage.dbName || '-' }}</n-descriptions-item>
<n-descriptions-item label="独立连接串">
{{ usage.dbDsnConfigured ? '已配置' : '未配置' }}
</n-descriptions-item>
<n-descriptions-item label="业务库状态">
<n-tag :type="usage.dbReachable ? 'success' : 'error'" :bordered="false">
{{ usage.dbReachable ? '可达' : '不可达' }}