1. 新增编辑页管理员账号展示与密码修改字段,仅编辑态可见 2. 新增商户管理员密码重置逻辑,完善编辑场景密码校验 3. 补充超管可见控制:业务库、连接串、管理员信息非超管脱敏 4. 修复schema未使用响应式值的问题
701 lines
23 KiB
Go
701 lines
23 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"
|
|
)
|
|
|
|
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{}
|
|
}
|
|
|
|
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...)
|
|
}
|
|
|
|
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 {
|
|
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().DbName,
|
|
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.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)
|
|
}
|
|
if len(in.CreatedAt) == 2 {
|
|
mod = mod.WhereBetween(dao.ImTenant.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
|
}
|
|
|
|
mod = mod.Page(in.Page, in.PerPage).
|
|
OrderDesc(dao.ImTenant.Columns().Id).
|
|
Hook(hook.MemberSummary)
|
|
|
|
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
|
|
// 业务库名与独立连接串仅超管可维护,非超管提交一律强制忽略
|
|
if !super {
|
|
in.DbName = ""
|
|
in.DbDsn = ""
|
|
}
|
|
|
|
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().State: in.State,
|
|
dao.ImTenant.Columns().UpdatedBy: userId,
|
|
}
|
|
if super {
|
|
data[dao.ImTenant.Columns().DbName] = in.DbName
|
|
data[dao.ImTenant.Columns().DbDsn] = in.DbDsn
|
|
}
|
|
if _, err = s.Model(ctx).
|
|
WherePri(in.Id).Data(data).Update(); err != nil {
|
|
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
|
|
}
|
|
return s.saveConfig(ctx, in, userId)
|
|
}
|
|
|
|
in.CreatedBy = userId
|
|
in.UpdatedBy = userId
|
|
id, err := s.Model(ctx, &handler.Option{FilterAuth: false}).
|
|
Fields(sysin.ImTenantInsertFields{}).
|
|
Data(in).InsertAndGetId()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
|
|
}
|
|
tenantID = id
|
|
in.Id = id
|
|
return s.saveConfig(ctx, in, userId)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 新增商户时自动创建绑定的后台管理员账号,失败则补偿回滚商户数据
|
|
if isCreate {
|
|
if err = s.createTenantAdminMember(ctx, super, in, tenantID); err != nil {
|
|
s.rollbackTenantCreation(ctx, tenantID)
|
|
return err
|
|
}
|
|
} 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
|
|
}
|
|
_, err = s.configModel(ctx).
|
|
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).Delete()
|
|
return
|
|
})
|
|
}
|
|
|
|
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 (s *sSysImTenant) saveConfig(ctx context.Context, in *sysin.ImTenantEditInp, userId int64) (err error) {
|
|
if in.MessageEncryptionEnabled == 1 {
|
|
count, queryErr := imtenant.RegistryDB().Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
|
|
Where("tenant_id", in.Id).
|
|
Where("status", consts.ImMessageKeyStatusActive).
|
|
Count()
|
|
if queryErr != nil {
|
|
return gerror.Wrap(queryErr, "检查商户消息密钥失败")
|
|
}
|
|
if count == 0 {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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).
|
|
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()
|
|
}
|
|
}
|
|
// 业务库名与独立连接串仅超管可见,非超管全部脱敏
|
|
if !isSuperTenantAdmin(ctx) {
|
|
res.DbName = ""
|
|
res.DbDsn = ""
|
|
res.DbDsnConfigured = false
|
|
}
|
|
return
|
|
}
|
|
|
|
// Status 更新IM租户状态
|
|
func (s *sSysImTenant) Status(ctx context.Context, in *sysin.ImTenantStatusInp) (err error) {
|
|
if err = in.Filter(ctx); err != nil {
|
|
return
|
|
}
|
|
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,
|
|
DbName: tenant.DbName,
|
|
DbDsnConfigured: tenant.DbDsn != "",
|
|
Warnings: []string{},
|
|
}
|
|
// 业务库名与连接串信息仅超管可见,非超管全部脱敏
|
|
if !isSuperTenantAdmin(ctx) {
|
|
res.DbName = ""
|
|
res.DbDsnConfigured = false
|
|
}
|
|
|
|
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)
|
|
res.UserCount = s.queryScalarInt64(ctx, db, "im_users.total", "SELECT COUNT(*) FROM im_users", &res.Warnings)
|
|
res.ActiveUserCount = s.queryScalarInt64(ctx, db, "im_users.active", "SELECT COUNT(*) FROM im_users WHERE state=0", &res.Warnings)
|
|
res.GroupCount = s.queryScalarInt64(ctx, db, "im_group_settings", "SELECT COUNT(*) FROM im_group_settings WHERE group_status<>'dissolved'", &res.Warnings)
|
|
res.DeviceCount = s.queryScalarInt64(ctx, db, "im_devices", "SELECT COUNT(*) FROM im_devices", &res.Warnings)
|
|
res.PushTokenCount = s.queryScalarInt64(ctx, db, "im_device_push_tokens", "SELECT COUNT(*) FROM im_device_push_tokens", &res.Warnings)
|
|
res.InviteUseCount = s.queryScalarInt64(ctx, db, "im_registration_invite_uses", "SELECT COUNT(*) FROM im_registration_invite_uses", &res.Warnings)
|
|
res.OutboxPendingCount = s.queryScalarInt64(ctx, db, "im_business_events_outbox", "SELECT COUNT(*) FROM im_business_events_outbox WHERE status='pending'", &res.Warnings)
|
|
return
|
|
}
|
|
|
|
func (s *sSysImTenant) queryScalarInt64(ctx context.Context, db gdb.DB, label, sql string, warnings *[]string) int64 {
|
|
value, err := db.GetValue(ctx, sql)
|
|
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 = 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
|
|
}
|