Files
im_manager/server/internal/logic/sys/im_tenant.go
T

673 lines
24 KiB
Go

// Package sys
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sys
import (
"context"
"fmt"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/library/contexts"
"hotgo/internal/library/hgorm/handler"
"hotgo/internal/library/hgorm/hook"
"hotgo/internal/library/imtenant"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/adminin"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"hotgo/utility/tree"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type sSysImTenant struct{}
// imManageRoleKey 商户级IM管理员角色标识
const imManageRoleKey = "imManage"
func NewSysImTenant() *sSysImTenant {
return &sSysImTenant{}
}
func init() {
service.RegisterSysImTenant(NewSysImTenant())
}
// Model IM租户ORM模型
func (s *sSysImTenant) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
return handler.Model(imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx), option...)
}
// 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 {
return
}
mod := s.Model(ctx).Fields(
dao.ImTenant.Columns().Id,
dao.ImTenant.Columns().Code,
dao.ImTenant.Columns().Name,
dao.ImTenant.Columns().TenantDesc,
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,
dao.ImTenant.Columns().CreatedAt,
dao.ImTenant.Columns().UpdatedAt,
)
if !isImPlatformAdmin(ctx, contexts.GetUserId(ctx)) {
tenantId := contexts.GetTenantId(ctx)
if tenantId <= 0 {
err = gerror.New("当前管理员未关联IM租户")
return
}
mod = mod.Where(dao.ImTenant.Columns().Id, tenantId)
}
if in.Id > 0 {
mod = mod.Where(dao.ImTenant.Columns().Id, in.Id)
}
if in.Code != "" {
mod = mod.WhereLike(dao.ImTenant.Columns().Code, "%"+in.Code+"%")
}
if in.Name != "" {
mod = mod.WhereLike(dao.ImTenant.Columns().Name, "%"+in.Name+"%")
}
if in.State != nil {
mod = mod.Where(dao.ImTenant.Columns().State, *in.State)
}
if len(in.CreatedAt) == 2 {
mod = mod.WhereBetween(dao.ImTenant.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
}
orderDesc := false
for _, sorter := range in.GetSorters() {
if sorter != nil && sorter.ColumnKey == "id" {
orderDesc = sorter.Order == "descend"
break
}
}
mod = mod.Page(in.Page, in.PerPage)
if orderDesc {
mod = mod.OrderDesc(dao.ImTenant.Columns().Id)
} else {
mod = mod.OrderAsc(dao.ImTenant.Columns().Id)
}
mod = mod.Hook(hook.MemberSummary)
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "获取IM租户列表失败,请稍后重试!")
return
}
return
}
// Add 新增IM租户
func (s *sSysImTenant) Add(ctx context.Context, in *sysin.ImTenantEditInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
if !isImPlatformAdmin(ctx, contexts.GetUserId(ctx)) {
return gerror.New("只有平台管理员可以新增商户")
}
in.Id = 0
return s.saveTenant(ctx, in)
}
// Edit 修改IM租户
func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
if in.Id <= 0 {
return gerror.New("商户ID不能为空")
}
if !isImPlatformAdmin(ctx, contexts.GetUserId(ctx)) {
tenantId := contexts.GetTenantId(ctx)
if tenantId <= 0 {
return gerror.New("当前管理员未关联IM租户")
}
if in.Id != tenantId {
return gerror.New("无权编辑其它商户")
}
}
return s.saveTenant(ctx, in)
}
// isSuperTenantAdmin 当前操作者是否为admin超管
func isSuperTenantAdmin(ctx context.Context) bool {
return service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx))
}
func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp) (err error) {
super := isSuperTenantAdmin(ctx)
isCreate := in.Id == 0
tenantID := in.Id
requestedState := in.State
if isCreate {
if requestedState != consts.ImTenantStateActive && requestedState != consts.ImTenantStateSuspended {
return gerror.New("新商户创建完成后的状态只能是正常或已停用")
}
if err = ensureTenantIDSequence(ctx); err != nil {
return err
}
in.State = consts.ImTenantStateProvisioning
}
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 {
return
}
userId := contexts.GetUserId(ctx)
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().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 nil
}
in.Id, err = nextTenantID(tx)
if err != nil {
return err
}
in.CreatedBy = userId
in.UpdatedBy = userId
_, err = tx.Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
Fields(sysin.ImTenantInsertFields{}).
Data(in).Insert()
if err != nil {
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
}
tenantID = in.Id
// 新建商户时复制安全策略模板为本商户初始策略(保存即生效模型)
if err = copyDefaultPolicyForTenant(ctx, tx, in.Id, userId); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
// 新增商户时自动创建绑定的后台管理员账号,失败则补偿回滚商户数据
if isCreate {
if err = s.createTenantAdminMember(ctx, super, in, tenantID); err != nil {
s.rollbackTenantCreation(ctx, tenantID)
return err
}
if err = provisionTenantMessageTables(ctx, tenantID); err != nil {
return gerror.Wrap(err, "商户已登记为初始化中,但业务分表创建失败")
}
if err = initializeTenantWorkbench(ctx, tenantID, in.Code); err != nil {
return gerror.Wrap(err, "商户业务分表已创建,但工作台初始化失败")
}
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).WherePri(tenantID).Data(g.Map{
dao.ImTenant.Columns().State: requestedState,
dao.ImTenant.Columns().UpdatedBy: contexts.GetUserId(ctx),
}).Update(); err != nil {
return gerror.Wrap(err, "商户分表已创建,但更新商户状态失败")
}
in.State = requestedState
} else if in.AdminPassword != "" {
// 编辑商户时填写了新密码则重置管理员密码,留空不修改。
if err = s.resetTenantAdminPassword(ctx, tenantID, in.AdminPassword); err != nil {
return err
}
}
_ = publishImPolicyEvent(ctx, tenantID, in.Code, "tenant.features.updated", "moderation", 0)
return nil
}
// createTenantAdminMember 为新增商户自动创建后台管理员账号(默认绑定imManage角色)
func (s *sSysImTenant) createTenantAdminMember(ctx context.Context, super bool, in *sysin.ImTenantEditInp, tenantId int64) (err error) {
var role *entity.AdminRole
if err = dao.AdminRole.Ctx(ctx).
Fields(dao.AdminRole.Columns().Id).
Where(dao.AdminRole.Columns().Key, imManageRoleKey).
Where(dao.AdminRole.Columns().Status, consts.StatusEnabled).
OrderAsc(dao.AdminRole.Columns().Id).
Scan(&role); err != nil {
return gerror.Wrap(err, "查询IM管理员角色失败,请稍后重试!")
}
if role == nil || role.Id <= 0 {
return gerror.New("IM管理员角色(imManage)未配置,请联系运维")
}
// 部门挂载:超管挂公司部门;非超管受部门数据权限限制,只能从自己部门的下级中选取
var deptId int64
if super {
companyDeptId, deptErr := dao.AdminDept.Ctx(ctx).
Fields(dao.AdminDept.Columns().Id).
Where(dao.AdminDept.Columns().Type, consts.DeptTypeCompany).
Where(dao.AdminDept.Columns().Status, consts.StatusEnabled).
OrderAsc(dao.AdminDept.Columns().Id).
Limit(1).
Value()
if deptErr != nil {
return gerror.Wrap(deptErr, "获取公司部门失败,请稍后重试!")
}
if companyDeptId.IsEmpty() {
return gerror.New("请先配置公司部门")
}
deptId = companyDeptId.Int64()
} else {
mb := contexts.GetUser(ctx)
if mb == nil || mb.DeptId <= 0 {
return gerror.New("获取当前用户部门失败")
}
subDeptId, deptErr := dao.AdminDept.Ctx(ctx).
Fields(dao.AdminDept.Columns().Id).
Where(dao.AdminDept.Columns().Status, consts.StatusEnabled).
WhereNot(dao.AdminDept.Columns().Id, mb.DeptId).
WhereLike(dao.AdminDept.Columns().Tree, "%"+tree.GetIdLabel(mb.DeptId)+"%").
OrderAsc(dao.AdminDept.Columns().Id).
Limit(1).
Value()
if deptErr != nil {
return gerror.Wrap(deptErr, "获取下级部门失败,请稍后重试!")
}
if subDeptId.IsEmpty() {
return gerror.New("当前部门下没有可用的下级部门来挂载商户管理员账号,请先配置下级部门")
}
deptId = subDeptId.Int64()
}
// 透传底层可读错误,便于前端定位
return service.AdminMember().Edit(ctx, &adminin.MemberEditInp{
TenantId: tenantId,
RoleId: role.Id,
Username: in.AdminUsername,
Password: in.AdminPassword,
RealName: in.Name + "管理员",
DeptId: deptId,
Status: consts.StatusEnabled,
})
}
// resetTenantAdminPassword 重置商户管理员账号的密码(编辑弹窗填写新密码时调用)
func (s *sSysImTenant) resetTenantAdminPassword(ctx context.Context, tenantId int64, password string) (err error) {
roleVal, err := dao.AdminRole.Ctx(ctx).
Fields(dao.AdminRole.Columns().Id).
Where(dao.AdminRole.Columns().Key, imManageRoleKey).
Where(dao.AdminRole.Columns().Status, consts.StatusEnabled).
Limit(1).
Value()
if err != nil {
return gerror.Wrap(err, "查询IM管理员角色失败,请稍后重试!")
}
if roleVal.IsEmpty() {
return gerror.New("IM管理员角色(imManage)未配置,请联系运维")
}
memberId, err := dao.AdminMember.Ctx(ctx).
Fields(dao.AdminMember.Columns().Id).
Where(dao.AdminMember.Columns().TenantId, tenantId).
Where(dao.AdminMember.Columns().RoleId, roleVal.Int64()).
OrderAsc(dao.AdminMember.Columns().Id).
Limit(1).
Value()
if err != nil {
return gerror.Wrap(err, "查询商户管理员账号失败,请稍后重试!")
}
if memberId.IsEmpty() {
return gerror.New("该商户暂无管理员账号,无法重置密码")
}
return service.AdminMember().ResetPwd(ctx, &adminin.MemberResetPwdInp{
Id: memberId.Int64(),
Password: password,
})
}
// 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
}
// 清理建户时复制的安全策略与宵禁规则,避免残留孤儿数据(商户未建成,硬删不留软删垃圾)
if _, err = tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).
Where(dao.ImSecurityPolicy.Columns().TenantId, tenantId).Unscoped().Delete(); err != nil {
return
}
_, err = tx.Model(dao.ImMessageCurfewRules.Table()).Safe().Ctx(ctx).
Where(dao.ImMessageCurfewRules.Columns().TenantId, tenantId).Unscoped().Delete()
return
})
}
// copyDefaultPolicyForTenant 新建商户时复制安全策略模板为本商户自己的初始策略。
// 模板未初始化时跳过(运行时由内部配置接口的默认值兜底),不阻断建户。
func copyDefaultPolicyForTenant(ctx context.Context, tx gdb.TX, tenantID, operator int64) (err error) {
tpl, err := loadPolicyTemplateRow(tx.Model(imPolicyTemplateTable).Safe().Ctx(ctx))
if err != nil {
return gerror.Wrap(err, "读取安全策略模板失败")
}
if tpl == nil {
return nil
}
pc := dao.ImSecurityPolicy.Columns()
now := gtime.Now()
data := g.Map{
pc.TenantId: tenantID,
pc.PolicyName: tpl["policy_name"].String(),
pc.PolicyVersion: 1,
pc.PublishStatus: consts.ImSecurityPublishPublished,
pc.PasswordPolicy: tpl["password_policy"].String(),
pc.LoginRateLimit: tpl["login_rate_limit"].String(),
pc.SensitiveWordPolicy: tpl["sensitive_word_policy"].String(),
pc.FileReviewPolicy: tpl["file_review_policy"].String(),
pc.RealnamePolicy: tpl["realname_policy"].String(),
pc.RiskChallengePolicy: tpl["risk_challenge_policy"].String(),
pc.GlobalFriendEnabled: tpl["global_friend_enabled"].Int(),
pc.FriendDisabledPolicy: tpl["friend_disabled_policy"].String(),
pc.MessageCurfewEnabled: tpl["message_curfew_enabled"].Int(),
pc.Remark: "由默认模板复制",
pc.PublishedAt: now,
pc.PublishedBy: operator,
pc.CreatedAt: now,
pc.CreatedBy: operator,
pc.UpdatedAt: now,
pc.UpdatedBy: operator,
}
result, err := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Data(data).Insert()
if err != nil {
return gerror.Wrap(err, "复制安全策略模板失败")
}
policyID, err := result.LastInsertId()
if err != nil {
return err
}
// 同步复制模板宵禁规则
rc := dao.ImMessageCurfewRules.Columns()
for _, rule := range templateCurfewRules(tpl) {
ruleData := g.Map{
rc.TenantId: tenantID,
rc.PolicyId: policyID,
rc.RuleCode: rule.RuleCode,
rc.RuleName: rule.RuleName,
rc.DateType: rule.DateType,
rc.CustomDates: policyJSON(rule.CustomDates, "[]"),
rc.Weekdays: policyJSON(rule.Weekdays, "[]"),
rc.Timezone: rule.Timezone,
rc.StartTime: rule.StartTime,
rc.EndTime: rule.EndTime,
rc.Scope: rule.Scope,
rc.WhitelistUserIds: policyJSON(rule.WhitelistUserIds, "[]"),
rc.WhitelistGroupIds: policyJSON(rule.WhitelistGroupIds, "[]"),
rc.Message: rule.Message,
rc.Status: rule.Status,
rc.Sort: rule.Sort,
rc.CreatedAt: now,
rc.CreatedBy: operator,
rc.UpdatedAt: now,
rc.UpdatedBy: operator,
}
if _, err = tx.Model(dao.ImMessageCurfewRules.Table()).Safe().Ctx(ctx).Data(ruleData).Insert(); err != nil {
return gerror.Wrap(err, "复制模板宵禁规则失败")
}
}
return nil
}
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
mod := s.Model(ctx).Where(dao.ImTenant.Columns().Code, code)
if id > 0 {
mod = mod.Where(dao.ImTenant.Columns().Id+"<>?", id)
}
count, err := mod.Count()
if err != nil {
return gerror.Wrap(err, "检查IM租户编码失败,请稍后重试!")
}
if count > 0 {
return gerror.New("租户编码已存在")
}
return
}
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", tenantId).
Where("status", consts.ImMessageKeyStatusActive).
Count()
if queryErr != nil {
return gerror.Wrap(queryErr, "检查商户消息密钥失败")
}
if count == 0 {
return gerror.New("启用消息加密前必须先在消息密钥管理中初始化有效密钥")
}
}
return nil
}
// View 获取IM租户指定信息
func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res *sysin.ImTenantViewModel, err error) {
if err = in.Filter(ctx); err != nil {
return
}
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
err = gerror.Wrap(err, "获取IM租户信息失败,请稍后重试!")
return
}
if res == nil {
return
}
// 查询商户自动创建的管理员账号(编辑弹窗展示)
roleVal, roleErr := dao.AdminRole.Ctx(ctx).
Fields(dao.AdminRole.Columns().Id).
Where(dao.AdminRole.Columns().Key, imManageRoleKey).
Where(dao.AdminRole.Columns().Status, consts.StatusEnabled).
Limit(1).
Value()
if roleErr == nil && !roleVal.IsEmpty() {
userVal, userErr := dao.AdminMember.Ctx(ctx).
Fields(dao.AdminMember.Columns().Username).
Where(dao.AdminMember.Columns().TenantId, in.Id).
Where(dao.AdminMember.Columns().RoleId, roleVal.Int64()).
OrderAsc(dao.AdminMember.Columns().Id).
Limit(1).
Value()
if userErr == nil && !userVal.IsEmpty() {
res.AdminUsername = userVal.String()
}
}
return
}
// Status 更新IM租户状态
func (s *sSysImTenant) Status(ctx context.Context, in *sysin.ImTenantStatusInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
if in.State == consts.ImTenantStateActive {
if err = provisionTenantMessageTables(ctx, in.Id); err != nil {
return gerror.Wrap(err, "启用商户前创建业务分表失败")
}
if err = initializeTenantWorkbench(ctx, in.Id, ""); err != nil {
return gerror.Wrap(err, "启用商户前初始化工作台失败")
}
}
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
dao.ImTenant.Columns().State: in.State,
dao.ImTenant.Columns().UpdatedBy: contexts.GetUserId(ctx),
}).Update(); err != nil {
err = gerror.Wrap(err, "更新IM租户状态失败,请稍后重试!")
return
}
return
}
func (s *sSysImTenant) Usage(ctx context.Context, in *sysin.ImTenantUsageInp) (res *sysin.ImTenantUsageModel, err error) {
if err = in.Filter(ctx); err != nil {
return
}
var tenant *entity.ImTenant
if err = s.Model(ctx).WherePri(in.Id).Scan(&tenant); err != nil {
return nil, gerror.Wrap(err, "获取IM租户信息失败,请稍后重试!")
}
if tenant == nil || tenant.Id <= 0 {
return nil, gerror.New("IM租户不存在")
}
res = &sysin.ImTenantUsageModel{
Id: tenant.Id,
Code: tenant.Code,
Name: tenant.Name,
Warnings: []string{},
}
db, _, dbErr := imtenant.DB(ctx, tenant.Id)
if dbErr != nil {
res.DbReachable = false
res.DbCheckError = dbErr.Error()
return res, nil
}
if _, pingErr := db.GetValue(ctx, "SELECT 1"); pingErr != nil {
res.DbReachable = false
res.DbCheckError = pingErr.Error()
return res, nil
}
res.DbReachable = true
res.SchemaVersion = s.queryScalarString(ctx, db, "kvmeta.version", "SELECT value FROM kvmeta WHERE key_='version' LIMIT 1", &res.Warnings)
tenantId := tenant.Id
res.UserCount = s.queryScalarInt64(ctx, db, "im_users.total", "SELECT COUNT(*) FROM im_users WHERE tenant_id=?", &res.Warnings, tenantId)
res.ActiveUserCount = s.queryScalarInt64(ctx, db, "im_users.active", "SELECT COUNT(*) FROM im_users WHERE tenant_id=? AND state=0", &res.Warnings, tenantId)
res.GroupCount = s.queryScalarInt64(ctx, db, "im_group_settings", "SELECT COUNT(*) FROM im_group_settings WHERE tenant_id=? AND group_status<>'dissolved'", &res.Warnings, tenantId)
res.DeviceCount = s.queryScalarInt64(ctx, db, "im_devices", "SELECT COUNT(*) FROM im_devices WHERE tenant_id=?", &res.Warnings, tenantId)
res.PushTokenCount = s.queryScalarInt64(ctx, db, "im_device_push_tokens", "SELECT COUNT(*) FROM im_device_push_tokens WHERE tenant_id=?", &res.Warnings, tenantId)
res.InviteUseCount = s.queryScalarInt64(ctx, db, "im_registration_invite_uses", "SELECT COUNT(*) FROM im_registration_invite_uses WHERE tenant_id=?", &res.Warnings, tenantId)
res.OutboxPendingCount = s.queryScalarInt64(ctx, db, "im_business_events_outbox", "SELECT COUNT(*) FROM im_business_events_outbox WHERE tenant_id=? AND status='pending'", &res.Warnings, tenantId)
return
}
func (s *sSysImTenant) queryScalarInt64(ctx context.Context, db gdb.DB, label, sql string, warnings *[]string, args ...any) int64 {
value, err := db.GetValue(ctx, sql, args...)
if err != nil {
*warnings = append(*warnings, label+": "+err.Error())
return 0
}
return value.Int64()
}
func (s *sSysImTenant) queryScalarString(ctx context.Context, db gdb.DB, label, sql string, warnings *[]string) string {
value, err := db.GetValue(ctx, sql)
if err != nil {
*warnings = append(*warnings, label+": "+err.Error())
return ""
}
return value.String()
}
func (s *sSysImTenant) DeletionCheck(ctx context.Context, in *sysin.ImTenantDeletionCheckInp) (res *sysin.ImTenantDeletionCheckModel, err error) {
if err = in.Filter(ctx); err != nil {
return
}
usage, err := s.Usage(ctx, &sysin.ImTenantUsageInp{Id: in.Id})
if err != nil {
return nil, err
}
res = &sysin.ImTenantDeletionCheckModel{
Id: usage.Id,
Code: usage.Code,
Name: usage.Name,
Blockers: []string{},
Warnings: usage.Warnings,
UserCount: usage.UserCount,
GroupCount: usage.GroupCount,
PendingOutbox: usage.OutboxPendingCount,
}
if !usage.DbReachable {
res.Blockers = append(res.Blockers, "租户业务库不可达:"+usage.DbCheckError)
}
if usage.UserCount > 0 {
res.Blockers = append(res.Blockers, fmt.Sprintf("租户仍有 %d 个用户", usage.UserCount))
}
if usage.GroupCount > 0 {
res.Blockers = append(res.Blockers, fmt.Sprintf("租户仍有 %d 个群", usage.GroupCount))
}
if usage.OutboxPendingCount > 0 {
res.Blockers = append(res.Blockers, fmt.Sprintf("租户仍有 %d 条待处理 Outbox", usage.OutboxPendingCount))
}
res.CanEnterDeleting = len(res.Blockers) == 0
return
}
func (s *sSysImTenant) EnterDeleting(ctx context.Context, in *sysin.ImTenantLifecycleInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
check, err := s.DeletionCheck(ctx, &sysin.ImTenantDeletionCheckInp{Id: in.Id})
if err != nil {
return err
}
if !check.CanEnterDeleting {
return gerror.New("租户未通过删除前检查,不能进入删除流程")
}
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
dao.ImTenant.Columns().State: consts.ImTenantStateDeleting,
dao.ImTenant.Columns().UpdatedBy: contexts.GetUserId(ctx),
}).Update(); err != nil {
return gerror.Wrap(err, "设置IM租户删除流程失败,请稍后重试!")
}
return
}
func (s *sSysImTenant) Restore(ctx context.Context, in *sysin.ImTenantLifecycleInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
if err = provisionTenantMessageTables(ctx, in.Id); err != nil {
return gerror.Wrap(err, "恢复商户前创建业务分表失败")
}
if err = initializeTenantWorkbench(ctx, in.Id, ""); err != nil {
return gerror.Wrap(err, "恢复商户前初始化工作台失败")
}
if _, err = s.Model(ctx).
WherePri(in.Id).
WhereIn(dao.ImTenant.Columns().State, []int{consts.ImTenantStateSuspended, consts.ImTenantStateDeleting}).
Data(g.Map{
dao.ImTenant.Columns().State: consts.ImTenantStateActive,
dao.ImTenant.Columns().UpdatedBy: contexts.GetUserId(ctx),
}).Update(); err != nil {
return gerror.Wrap(err, "恢复IM租户失败,请稍后重试!")
}
return
}