311 lines
9.2 KiB
Go
311 lines
9.2 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"
|
|
"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",
|
|
}
|
|
}
|