// 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" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/contexts" "hotgo/internal/library/hgorm/handler" "hotgo/internal/library/hgorm/hook" "hotgo/internal/library/imkafka" "hotgo/internal/library/imtenant" "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "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/util/gconv" ) type sSysImTenant struct{} 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(ctx).Model(dao.ImTenant.Table()).Safe().Ctx(ctx), option...) } func (s *sSysImTenant) configModel(ctx context.Context) *gdb.Model { return imtenant.RegistryDB(ctx).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 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 != "" { 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 } 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 []*entity.ImTenantConfig if err = s.configModel(ctx). WhereIn(dao.ImTenantConfig.Columns().TenantId, ids). Scan(&configs); err != nil { return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!") } configMap := make(map[int64]*entity.ImTenantConfig, 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 } for _, item := range list { if conf, ok := configMap[item.Id]; ok { item.MaxUsers = conf.MaxUsers item.MaxGroups = conf.MaxGroups item.MaxGroupMembers = conf.MaxGroupMembers } item.DbDsnConfigured = dsnMap[item.Id] != "" } return } // Edit 修改/新增IM租户 func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err error) { if err = in.Filter(ctx); err != nil { return } err = imtenant.RegistryDB(ctx).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 if _, err = s.Model(ctx). Fields(sysin.ImTenantUpdateFields{}). WherePri(in.Id).Data(in).Update(); err != nil { return gerror.Wrap(err, "修改IM租户失败,请稍后重试!") } return s.saveConfig(ctx, in.Id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, userId) } in.CreatedBy = userId id, err := s.Model(ctx, &handler.Option{FilterAuth: false}). Fields(sysin.ImTenantInsertFields{}). Data(in).InsertAndGetId() if err != nil { return gerror.Wrap(err, "新增IM租户失败,请稍后重试!") } return s.saveConfig(ctx, id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, userId) }) if err != nil { return err } s.publishTenantConfigEvent(ctx, in.Code, in.Id, consts.ImConfigEventTenantUpdated, "tenant") s.publishTenantConfigEvent(ctx, in.Code, in.Id, consts.ImConfigEventTenantLimitsUpdated, "tenant_limits") 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 (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers uint64, maxGroups uint64, maxGroupMembers uint, userId int64) (err error) { var conf *entity.ImTenantConfig if err = s.configModel(ctx). Where(dao.ImTenantConfig.Columns().TenantId, tenantId). Scan(&conf); err != nil { return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!") } data := g.Map{ dao.ImTenantConfig.Columns().MaxUsers: maxUsers, dao.ImTenantConfig.Columns().MaxGroups: maxGroups, dao.ImTenantConfig.Columns().MaxGroupMembers: maxGroupMembers, } 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, dao.ImTenantConfig.Columns().UpdatedBy, ). WherePri(conf.Id).Data(data).Update(); err != nil { return gerror.Wrap(err, "修改IM租户配置失败,请稍后重试!") } return } data[dao.ImTenantConfig.Columns().TenantId] = tenantId data[dao.ImTenantConfig.Columns().CreatedBy] = userId if _, err = s.configModel(ctx). Fields( dao.ImTenantConfig.Columns().TenantId, dao.ImTenantConfig.Columns().MaxUsers, dao.ImTenantConfig.Columns().MaxGroups, dao.ImTenantConfig.Columns().MaxGroupMembers, dao.ImTenantConfig.Columns().CreatedBy, ). 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 *entity.ImTenantConfig 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.DbDsnConfigured = res.DbDsn != "" 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 } var tenant *entity.ImTenant if scanErr := s.Model(ctx).Fields(dao.ImTenant.Columns().Id, dao.ImTenant.Columns().Code).WherePri(in.Id).Scan(&tenant); scanErr == nil && tenant != nil { s.publishTenantConfigEvent(ctx, tenant.Code, tenant.Id, consts.ImConfigEventTenantStatusChanged, "tenant_status") } return } func (s *sSysImTenant) publishTenantConfigEvent(ctx context.Context, tenantCode string, tenantId int64, eventType string, bizId string) { user := contexts.GetUser(ctx) actorName := "" if user != nil { actorName = user.RealName } if err := imkafka.PublishConfigEvent(ctx, imkafka.ConfigEventInput{ TenantID: tenantId, TenantCode: tenantCode, EventType: eventType, BizID: bizId, Group: "tenant", Keys: []string{"tenant", "limits", "features"}, Actor: &imkafka.Actor{ Type: "admin", ID: gconv.String(contexts.GetUserId(ctx)), DisplayName: actorName, }, CreatedBy: contexts.GetUserId(ctx), }); err != nil { g.Log().Warningf(ctx, "发送IM租户配置Kafka事件失败 tenant:%s type:%s err:%+v", tenantCode, eventType, err) } }