// 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/library/contexts" "hotgo/internal/library/imkafka" "hotgo/internal/library/imtenant" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "strconv" "strings" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" ) type sSysImUser struct{} func NewSysImUser() *sSysImUser { return &sSysImUser{} } func init() { service.RegisterSysImUser(NewSysImUser()) } // List 获取IM用户列表 func (s *sSysImUser) List(ctx context.Context, in *sysin.ImUserListInp) (list []*sysin.ImUserListModel, totalCount int, err error) { if err = in.Filter(ctx); err != nil { return } db, tenant, err := imtenant.DB(ctx, in.TenantId) if err != nil { return } mod := s.listModel(ctx, db) mod = s.applyListWhere(mod, in) mod = mod.Page(in.Page, in.PerPage).OrderDesc("u.id") if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { err = gerror.Wrap(err, "获取IM用户列表失败,请稍后重试!") return } if len(list) == 0 { return } for _, item := range list { item.TenantId = tenant.Id item.TenantName = tenant.Name } if err = s.fillAuthAndCredentials(ctx, db, list); err != nil { return } return } func (s *sSysImUser) listModel(ctx context.Context, db gdb.DB) *gdb.Model { return db.Model("im_users u").Safe().Ctx(ctx). LeftJoin("im_user_profiles up", "up.user_id = u.id"). LeftJoin("im_user_security us", "us.user_id = u.id"). Fields( "u.id user_id", "u.state", "u.lastseen last_seen", "u.useragent user_agent", "u.createdat created_at", "u.updatedat updated_at", "up.nickname", "up.public_account", "up.gender", "up.region", "us.auth_version", "us.password_updated_at", ) } func (s *sSysImUser) applyListWhere(mod *gdb.Model, in *sysin.ImUserListInp) *gdb.Model { if in.UserId > 0 { mod = mod.Where("u.id", in.UserId) } if in.Nickname != "" { mod = mod.WhereLike("up.nickname", "%"+in.Nickname+"%") } if in.AuthName != "" { mod = mod.Where("EXISTS (SELECT 1 FROM im_auth a WHERE a.userid = u.id AND a.uname LIKE ?)", "%"+in.AuthName+"%") } if in.State != nil { mod = mod.Where("u.state", *in.State) } if len(in.CreatedAt) == 2 { mod = mod.WhereBetween("u.createdat", in.CreatedAt[0], in.CreatedAt[1]) } if len(in.LastSeen) == 2 { mod = mod.WhereBetween("u.lastseen", in.LastSeen[0], in.LastSeen[1]) } return mod } func (s *sSysImUser) fillAuthAndCredentials(ctx context.Context, db gdb.DB, list []*sysin.ImUserListModel) (err error) { ids := make([]int64, 0, len(list)) userMap := make(map[int64]*sysin.ImUserListModel, len(list)) for _, item := range list { ids = append(ids, item.UserId) userMap[item.UserId] = item } authRows, err := db.Model("im_auth").Safe().Ctx(ctx). Fields("userid", "scheme", "uname"). WhereIn("userid", ids). OrderAsc("scheme").OrderAsc("uname"). All() if err != nil { return gerror.Wrap(err, "获取IM用户认证标识失败,请稍后重试!") } authMap := make(map[int64][]string) for _, row := range authRows { userId := row["userid"].Int64() scheme := row["scheme"].String() uname := row["uname"].String() authMap[userId] = append(authMap[userId], scheme+":"+uname) } credentialRows, err := db.Model("im_credentials").Safe().Ctx(ctx). Fields("userid", "method", "value", "done"). WhereIn("userid", ids). WhereNull("deletedat"). OrderAsc("method").OrderAsc("value"). All() if err != nil { return gerror.Wrap(err, "获取IM用户凭证失败,请稍后重试!") } credentialMap := make(map[int64][]string) for _, row := range credentialRows { userId := row["userid"].Int64() method := row["method"].String() value := row["value"].String() done := row["done"].Bool() if !done { value += "(未验证)" } credentialMap[userId] = append(credentialMap[userId], method+":"+value) } for userId, item := range userMap { item.AuthNames = strings.Join(authMap[userId], ", ") item.Credentials = strings.Join(credentialMap[userId], ", ") } return } // View 获取IM用户指定信息 func (s *sSysImUser) View(ctx context.Context, in *sysin.ImUserViewInp) (res *sysin.ImUserViewModel, err error) { if err = in.Filter(ctx); err != nil { return } db, tenant, err := imtenant.DB(ctx, in.TenantId) if err != nil { return } fields := append(s.viewFields(), "u.access access_json", "u.public profile_json", "u.trusted trusted_json", "u.tags tags_json") err = db.Model("im_users u").Safe().Ctx(ctx). LeftJoin("im_user_profiles up", "up.user_id = u.id"). LeftJoin("im_user_security us", "us.user_id = u.id"). Fields(fields...). Where("u.id", in.UserId). Scan(&res) if err != nil { return nil, gerror.Wrap(err, "获取IM用户信息失败,请稍后重试!") } if res == nil { return nil, nil } res.TenantId = tenant.Id res.TenantName = tenant.Name list := []*sysin.ImUserListModel{&res.ImUserListModel} if err = s.fillAuthAndCredentials(ctx, db, list); err != nil { return nil, err } return } // Ban 通过Kafka封禁IM用户 func (s *sSysImUser) Ban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserBan) } // Unban 通过Kafka解封IM用户 func (s *sSysImUser) Unban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnban) } // Freeze 通过Kafka冻结IM用户 func (s *sSysImUser) Freeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserFreeze) } // Unfreeze 通过Kafka解冻IM用户 func (s *sSysImUser) Unfreeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnfreeze) } // DeviceKick 通过Kafka踢出IM用户设备 func (s *sSysImUser) DeviceKick(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { if err = in.FilterDevice(ctx); err != nil { return nil, err } return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserDeviceKick) } // LogoutAll 通过Kafka踢出IM用户全部在线会话 func (s *sSysImUser) LogoutAll(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserLogoutAll) } // AccountCancel 通过Kafka注销IM用户账号 func (s *sSysImUser) AccountCancel(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserCancel) } // AccountDelete 通过Kafka删除IM用户账号 func (s *sSysImUser) AccountDelete(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) { in.Hard = true return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserDelete) } func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImUserGovernanceInp, eventType string) (*sysin.ImUserCommandModel, error) { if err := in.Filter(ctx); err != nil { return nil, err } _, tenant, err := imtenant.DB(ctx, in.TenantId) if err != nil { return nil, err } user := contexts.GetUser(ctx) actorName := "" if user != nil { actorName = user.RealName } payload := map[string]any{ "actor_user_id": strconv.FormatInt(in.ActorUserId, 10), "user_id": strconv.FormatInt(in.UserId, 10), "target_user_id": strconv.FormatInt(in.UserId, 10), "reason": in.Reason, } if in.DeviceId != "" { payload["device_id"] = in.DeviceId } if eventType == consts.ImAdminCommandUserCancel || eventType == consts.ImAdminCommandUserDelete { payload["hard"] = in.Hard } command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{ TenantID: tenant.Id, TenantCode: tenant.Code, EventType: eventType, BizType: "user", BizID: strconv.FormatInt(in.UserId, 10), Actor: &imkafka.Actor{ Type: "admin", ID: strconv.FormatInt(contexts.GetUserId(ctx), 10), DisplayName: actorName, }, Payload: payload, CreatedBy: contexts.GetUserId(ctx), }) if err != nil { return nil, err } return &sysin.ImUserCommandModel{ CommandID: command.CommandID, EventID: command.EventID, Status: command.Status, TenantID: command.TenantID, TenantCode: command.TenantCode, EventType: command.EventType, IdempotencyKey: command.IdempotencyKey, }, nil } func (s *sSysImUser) viewFields() []interface{} { return []interface{}{ "u.id user_id", "u.state", "u.lastseen last_seen", "u.useragent user_agent", "u.createdat created_at", "u.updatedat updated_at", "up.nickname", "up.public_account", "up.gender", "up.region", "up.signature", "up.avatar_file_id", "CAST(up.birth_date AS CHAR) birth_date", "up.version profile_version", "us.auth_version", "us.password_updated_at", } }