feat: refine IM admin management workflows

This commit is contained in:
richjohnrambo
2026-09-13 23:25:53 +08:00
parent 6e65935e3c
commit 8b30b8241c
15 changed files with 2128 additions and 525 deletions
+44 -339
View File
@@ -88,7 +88,7 @@ func (s *sSysImGroup) groupModel(ctx context.Context, db gdb.DB) *gdb.Model {
"t.trusted trusted_json",
"t.tags tags_json",
"t.aux aux_json",
"COALESCE(gs.name, '') group_name",
"COALESCE(NULLIF(gs.name, ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.fn')), ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.name')), ''), t.name) group_name",
"COALESCE(gs.avatar_file_id, '') avatar_file_id",
"COALESCE(gs.description, '') description",
"COALESCE(gs.group_status, '') group_status",
@@ -161,7 +161,7 @@ func (s *sSysImGroup) View(ctx context.Context, in *sysin.ImGroupViewInp) (res *
"t.aux aux_json",
"t.access access_json",
"t.usebt use_bt",
"COALESCE(gs.name, '') group_name",
"COALESCE(NULLIF(gs.name, ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.fn')), ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.name')), ''), t.name) group_name",
"COALESCE(gs.avatar_file_id, '') avatar_file_id",
"COALESCE(gs.description, '') description",
"COALESCE(gs.group_status, '') group_status",
@@ -349,50 +349,24 @@ func (s *sSysImGroup) Governance(ctx context.Context, in *sysin.ImGroupTopicInp)
}
// GovernanceUpdate 更新IM群治理配置
func (s *sSysImGroup) GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (err error) {
func (s *sSysImGroup) GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
if err = in.Filter(ctx); err != nil {
return
return nil, err
}
db, _, err := resolveImTenantDB(ctx, in.TenantId)
if err != nil {
return err
payload := map[string]any{
"topic": in.Topic,
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
"governance": map[string]any{
"join_approval": in.JoinApproval == 1,
"mute_all": in.MuteAll == 1,
"member_upload_files": in.MemberUploadFiles == 1,
"member_private_chat": in.MemberPrivateChat == 1,
"member_invite": in.MemberInvite == 1,
"max_member_count": in.MaxMemberCount,
"max_admin_count": in.MaxAdminCount,
},
}
actor := contexts.GetUserId(ctx)
requestId := gctx.CtxId(ctx)
if requestId == "" {
requestId = guid.S()
}
now := gtime.Now()
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
before, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
if txErr != nil {
return txErr
}
if before == nil {
return gerror.New("群治理配置不存在")
}
if before["group_status"].String() == consts.ImGroupStatusDissolved {
return gerror.New("已解散群不能修改治理配置")
}
if _, txErr = tx.Exec(`UPDATE im_group_settings SET
join_approval=?,mute_all=?,member_upload_files=?,member_private_chat=?,member_invite=?,
max_member_count=?,max_admin_count=?,version=version+1,updated_at=?
WHERE topic=?`,
in.JoinApproval, in.MuteAll, in.MemberUploadFiles, in.MemberPrivateChat, in.MemberInvite,
in.MaxMemberCount, in.MaxAdminCount, now, in.Topic); txErr != nil {
return txErr
}
after, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
if txErr != nil {
return txErr
}
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, 0, "governance_update", "group_settings", in.Topic, before.Map(), after.Map(), requestId); txErr != nil {
return txErr
}
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.governance_updated", g.Map{
"topic": in.Topic, "operator_id": actor, "request_id": requestId,
})
})
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupGovernUpdate, payload, 0)
}
func (s *sSysImGroup) ProfileUpdate(ctx context.Context, in *sysin.ImGroupProfileUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
@@ -412,101 +386,21 @@ func (s *sSysImGroup) ProfileUpdate(ctx context.Context, in *sysin.ImGroupProfil
}
// Dissolve 解散IM群
func (s *sSysImGroup) Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (err error) {
func (s *sSysImGroup) Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (res *sysin.ImGroupCommandModel, err error) {
if err = in.Filter(ctx); err != nil {
return
return nil, err
}
db, _, err := resolveImTenantDB(ctx, in.TenantId)
if err != nil {
return err
payload := map[string]any{
"topic": in.Topic,
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
"reason": in.Reason,
}
actor := contexts.GetUserId(ctx)
requestId := gctx.CtxId(ctx)
if requestId == "" {
requestId = guid.S()
}
now := gtime.Now()
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
before, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
if txErr != nil {
return txErr
}
if before == nil {
return gerror.New("群治理配置不存在")
}
if before["group_status"].String() == consts.ImGroupStatusDissolved {
return gerror.New("群已解散")
}
result, txErr := tx.Exec(`UPDATE im_group_settings
SET group_status=?,dissolved_by=?,dissolved_at=?,version=version+1,updated_at=?
WHERE topic=? AND group_status<>?`,
consts.ImGroupStatusDissolved, actor, now, now, in.Topic, consts.ImGroupStatusDissolved)
if txErr != nil {
return txErr
}
if affected, _ := result.RowsAffected(); affected == 0 {
return gerror.New("群已解散")
}
after, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
if txErr != nil {
return txErr
}
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, 0, "group_dissolve", "group", in.Topic, before.Map(), after.Map(), requestId); txErr != nil {
return txErr
}
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.dissolved", g.Map{
"topic": in.Topic, "operator_id": actor, "request_id": requestId, "reason": in.Reason,
})
})
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupDissolve, payload, 0)
}
// Mute 禁言IM群成员
func (s *sSysImGroup) Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
db, _, err := resolveImTenantDB(ctx, in.TenantId)
if err != nil {
return err
}
actor := contexts.GetUserId(ctx)
requestId := gctx.CtxId(ctx)
if requestId == "" {
requestId = guid.S()
}
now := gtime.Now()
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
if txErr := s.ensureActiveGroupMember(ctx, tx, in.Topic, in.UserId); txErr != nil {
return txErr
}
before, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
muteId, txErr := imtenant.NextSnowflakeID(ctx)
if txErr != nil {
return gerror.Wrap(txErr, "生成禁言ID失败")
}
if _, txErr = tx.Exec(`INSERT INTO im_group_mutes
(id,topic,user_id,muted_by,muted_at,expires_at,reason,status,version)
VALUES(?,?,?,?,?,?,?, ?,1)
ON DUPLICATE KEY UPDATE muted_by=VALUES(muted_by),muted_at=VALUES(muted_at),
expires_at=VALUES(expires_at),reason=VALUES(reason),status=VALUES(status),
revoked_by=NULL,revoked_at=NULL,version=version+1,updated_at=CURRENT_TIMESTAMP(3)`,
muteId, in.Topic, in.UserId, actor, now, in.ExpiresAt, in.Reason, consts.ImGroupMuteStatusActive); txErr != nil {
return txErr
}
after, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, "member_mute", "member", fmt.Sprintf("%d", in.UserId), recordMap(before), recordMap(after), requestId); txErr != nil {
return txErr
}
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.mute.updated", g.Map{
"topic": in.Topic, "user_id": in.UserId, "muted": true, "expires_at": in.ExpiresAt, "request_id": requestId,
})
})
func (s *sSysImGroup) Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error) {
return s.MemberMute(ctx, in)
}
func (s *sSysImGroup) MemberMute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error) {
@@ -526,49 +420,8 @@ func (s *sSysImGroup) MemberMute(ctx context.Context, in *sysin.ImGroupMuteInp)
}
// Unmute 解除IM群成员禁言
func (s *sSysImGroup) Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (err error) {
if err = in.Filter(ctx); err != nil {
return
}
db, _, err := resolveImTenantDB(ctx, in.TenantId)
if err != nil {
return err
}
actor := contexts.GetUserId(ctx)
requestId := gctx.CtxId(ctx)
if requestId == "" {
requestId = guid.S()
}
now := gtime.Now()
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
before, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if before == nil || before["status"].String() != consts.ImGroupMuteStatusActive {
return gerror.New("成员未处于禁言状态")
}
result, txErr := tx.Exec(`UPDATE im_group_mutes
SET status=?,revoked_by=?,revoked_at=?,version=version+1,updated_at=?
WHERE topic=? AND user_id=? AND status=?`,
consts.ImGroupMuteStatusRevoked, actor, now, now, in.Topic, in.UserId, consts.ImGroupMuteStatusActive)
if txErr != nil {
return txErr
}
if affected, _ := result.RowsAffected(); affected == 0 {
return gerror.New("成员未处于禁言状态")
}
after, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, "member_unmute", "member", fmt.Sprintf("%d", in.UserId), recordMap(before), recordMap(after), requestId); txErr != nil {
return txErr
}
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.mute.updated", g.Map{
"topic": in.Topic, "user_id": in.UserId, "muted": false, "request_id": requestId,
})
})
func (s *sSysImGroup) Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error) {
return s.MemberUnmute(ctx, in)
}
func (s *sSysImGroup) MemberUnmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error) {
@@ -677,77 +530,18 @@ func (s *sSysImGroup) resolveJoinRequest(ctx context.Context, in *sysin.ImGroupJ
}
// RoleUpdate 更新IM群成员角色
func (s *sSysImGroup) RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (err error) {
func (s *sSysImGroup) RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
if err = in.Filter(ctx); err != nil {
return
return nil, err
}
if in.Role != consts.ImGroupRoleAdmin && in.Role != consts.ImGroupRoleMember {
return gerror.New("只能设置管理员或普通成员")
switch in.Role {
case consts.ImGroupRoleAdmin:
return s.AdminSet(ctx, in)
case consts.ImGroupRoleMember:
return s.AdminRemove(ctx, in)
default:
return nil, gerror.New("只能设置管理员或普通成员")
}
db, _, err := resolveImTenantDB(ctx, in.TenantId)
if err != nil {
return err
}
actor := contexts.GetUserId(ctx)
requestId := gctx.CtxId(ctx)
if requestId == "" {
requestId = guid.S()
}
modeGiven := imGroupModeMember
action := "member_role_member"
if in.Role == consts.ImGroupRoleAdmin {
modeGiven = imGroupModeAdmin
action = "member_role_admin"
}
now := gtime.Now()
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
if err := s.ensureActiveGroup(ctx, tx, in.Topic); err != nil {
return err
}
before, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if before == nil || before["member_status"].String() != consts.ImGroupMemberStatusActive {
return gerror.New("成员不在群内或不是正常状态")
}
if before["role"].String() == consts.ImGroupRoleOwner {
return gerror.New("群主角色请通过群主转让处理")
}
if in.Role == consts.ImGroupRoleAdmin {
if txErr = s.ensureAdminLimit(ctx, tx, in.Topic, in.UserId); txErr != nil {
return txErr
}
}
subResult, txErr := tx.Exec(`UPDATE im_subscriptions SET modegiven=?,updatedat=?
WHERE topic=? AND userid=? AND deletedat IS NULL`, modeGiven, now, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if affected, _ := subResult.RowsAffected(); affected == 0 {
return gerror.New("成员通信订阅不存在")
}
result, txErr := tx.Exec(`UPDATE im_group_members
SET role=?,version=version+1,updated_at=?
WHERE topic=? AND user_id=? AND member_status=?`,
in.Role, now, in.Topic, in.UserId, consts.ImGroupMemberStatusActive)
if txErr != nil {
return txErr
}
if affected, _ := result.RowsAffected(); affected == 0 {
return gerror.New("成员不在群内或不是正常状态")
}
after, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, action, "member", fmt.Sprintf("%d", in.UserId), recordMap(before), recordMap(after), requestId); txErr != nil {
return txErr
}
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.role.updated", g.Map{
"topic": in.Topic, "user_id": in.UserId, "role": in.Role, "request_id": requestId,
})
})
}
func (s *sSysImGroup) AdminSet(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
@@ -775,105 +569,16 @@ func (s *sSysImGroup) AdminRemove(ctx context.Context, in *sysin.ImGroupRoleUpda
}
// OwnerTransfer 转让IM群主
func (s *sSysImGroup) OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (err error) {
func (s *sSysImGroup) OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (res *sysin.ImGroupCommandModel, err error) {
if err = in.Filter(ctx); err != nil {
return
return nil, err
}
db, _, err := resolveImTenantDB(ctx, in.TenantId)
if err != nil {
return err
payload := map[string]any{
"topic": in.Topic,
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
"target_user_id": strconv.FormatInt(in.UserId, 10),
}
actor := contexts.GetUserId(ctx)
requestId := gctx.CtxId(ctx)
if requestId == "" {
requestId = guid.S()
}
now := gtime.Now()
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
beforeGroup, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
if txErr != nil {
return txErr
}
if beforeGroup == nil {
return gerror.New("群治理配置不存在")
}
if beforeGroup["group_status"].String() == consts.ImGroupStatusDissolved {
return gerror.New("已解散群不能转让群主")
}
previousOwner := beforeGroup["owner_user_id"].Int64()
if previousOwner <= 0 {
return gerror.New("原群主不存在")
}
if previousOwner == in.UserId {
return gerror.New("新群主不能与原群主相同")
}
if txErr = s.ensureActiveGroupMember(ctx, tx, in.Topic, previousOwner); txErr != nil {
return txErr
}
if txErr = s.ensureActiveGroupMember(ctx, tx, in.Topic, in.UserId); txErr != nil {
return txErr
}
beforeOld, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, previousOwner)
if txErr != nil {
return txErr
}
beforeNew, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
oldSubResult, txErr := tx.Exec(`UPDATE im_subscriptions SET modegiven=?,updatedat=?
WHERE topic=? AND userid=? AND deletedat IS NULL`, imGroupModeAdmin, now, in.Topic, previousOwner)
if txErr != nil {
return txErr
}
if affected, _ := oldSubResult.RowsAffected(); affected == 0 {
return gerror.New("原群主通信订阅不存在")
}
if _, txErr = tx.Exec(`UPDATE im_group_members SET role=?,version=version+1,updated_at=?
WHERE topic=? AND user_id=? AND member_status=?`, consts.ImGroupRoleAdmin, now, in.Topic, previousOwner, consts.ImGroupMemberStatusActive); txErr != nil {
return txErr
}
newSubResult, txErr := tx.Exec(`UPDATE im_subscriptions SET modegiven=?,updatedat=?
WHERE topic=? AND userid=? AND deletedat IS NULL`, imGroupModeOwner, now, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if affected, _ := newSubResult.RowsAffected(); affected == 0 {
return gerror.New("新群主通信订阅不存在")
}
if _, txErr = tx.Exec(`UPDATE im_group_members SET role=?,version=version+1,updated_at=?
WHERE topic=? AND user_id=? AND member_status=?`, consts.ImGroupRoleOwner, now, in.Topic, in.UserId, consts.ImGroupMemberStatusActive); txErr != nil {
return txErr
}
if _, txErr = tx.Exec(`UPDATE im_topics SET owner=?,updatedat=? WHERE name=?`, in.UserId, now, in.Topic); txErr != nil {
return txErr
}
if _, txErr = tx.Exec(`UPDATE im_group_settings SET owner_user_id=?,version=version+1,updated_at=? WHERE topic=?`, in.UserId, now, in.Topic); txErr != nil {
return txErr
}
afterGroup, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
if txErr != nil {
return txErr
}
afterOld, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, previousOwner)
if txErr != nil {
return txErr
}
afterNew, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
if txErr != nil {
return txErr
}
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, "owner_transfer", "group", in.Topic, g.Map{
"group": recordMap(beforeGroup), "previous_owner": recordMap(beforeOld), "new_owner": recordMap(beforeNew),
}, g.Map{
"group": recordMap(afterGroup), "previous_owner": recordMap(afterOld), "new_owner": recordMap(afterNew),
}, requestId); txErr != nil {
return txErr
}
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.owner.transferred", g.Map{
"topic": in.Topic, "previous_owner": previousOwner, "owner": in.UserId, "request_id": requestId,
})
})
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupOwnerTransfer, payload, 0)
}
func (s *sSysImGroup) publishGroupCommand(ctx context.Context, tenantId int64, topic string, eventType string, payload map[string]any, version int64) (*sysin.ImGroupCommandModel, error) {
+51 -82
View File
@@ -16,7 +16,6 @@ import (
"hotgo/internal/dao"
"hotgo/internal/library/contexts"
"hotgo/internal/library/imadmin"
"hotgo/internal/library/imkafka"
"hotgo/internal/library/imtenant"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
@@ -483,21 +482,21 @@ func (s *sSysImUser) ResetPassword(ctx context.Context, in *sysin.ImUserPassword
if err = in.Filter(ctx); err != nil {
return nil, err
}
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
if err != nil {
return nil, err
}
userId, err := sysin.ParseOptionalInt64(in.UserId)
if err != nil {
return nil, err
}
command, err := s.publishPasswordResetCommand(ctx, tenant, userId, in.NewPassword)
res, err = s.syncPasswordReset(ctx, in, userId)
result := "success"
if err != nil {
result = "failed"
}
_ = writeImAdminAudit(ctx, tenant.Id, "user_password_reset", "user", userId, result, nil)
return command, err
tenantId := in.TenantId
if res != nil && res.TenantID > 0 {
tenantId = res.TenantID
}
_ = writeImAdminAudit(ctx, tenantId, "user_password_reset", "user", userId, result, nil)
return res, err
}
func (s *sSysImUser) RevokeSessions(ctx context.Context, in *sysin.ImUserRevokeSessionsInp) (err error) {
@@ -686,59 +685,53 @@ func (s *sSysImUser) Devices(ctx context.Context, in *sysin.ImUserDeviceListInp)
}
func (s *sSysImUser) Ban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserBan)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserBan)
}
func (s *sSysImUser) Unban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnban)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnban)
}
func (s *sSysImUser) Freeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserFreeze)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserFreeze)
}
func (s *sSysImUser) Unfreeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnfreeze)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnfreeze)
}
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)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserDeviceKick)
}
func (s *sSysImUser) LogoutAll(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserLogoutAll)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserLogoutAll)
}
func (s *sSysImUser) AccountCancel(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserCancel)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserCancel)
}
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)
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserDelete)
}
func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImUserGovernanceInp, eventType string) (*sysin.ImUserCommandModel, error) {
func (s *sSysImUser) syncGovernanceCommand(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)
tenant, err := resolveImAdminTenant(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,
"event_type": eventType,
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
"reason": in.Reason,
}
if in.DeviceId != "" {
payload["device_id"] = in.DeviceId
@@ -746,75 +739,51 @@ func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImU
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),
})
res, err := s.syncUserAction(ctx, tenant, in.UserId, eventType, payload)
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
return res, nil
}
func (s *sSysImUser) publishPasswordResetCommand(ctx context.Context, tenant *entity.ImTenant, userId int64, newPassword string) (*sysin.ImUserCommandModel, error) {
passwordHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
func (s *sSysImUser) syncPasswordReset(ctx context.Context, in *sysin.ImUserPasswordResetInp, userId int64) (*sysin.ImUserCommandModel, error) {
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
if err != nil {
return nil, err
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(in.NewPassword), bcrypt.DefaultCost)
if err != nil {
return nil, gerror.Wrap(err, "生成密码摘要失败")
}
user := contexts.GetUser(ctx)
actorName := ""
if user != nil {
actorName = user.RealName
}
payload := map[string]any{
"operator_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
"user_id": strconv.FormatInt(userId, 10),
"target_user_id": strconv.FormatInt(userId, 10),
"password_hash": base64.StdEncoding.EncodeToString(passwordHash),
"event_type": consts.ImAdminCommandUserPasswordReset,
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
"password_hash": base64.StdEncoding.EncodeToString(passwordHash),
}
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
TenantID: tenant.Id,
TenantCode: tenant.Code,
EventType: consts.ImAdminCommandUserPasswordReset,
BizType: "user",
BizID: strconv.FormatInt(userId, 10),
Actor: &imkafka.Actor{
Type: "admin",
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
DisplayName: actorName,
},
Payload: payload,
CreatedBy: contexts.GetUserId(ctx),
})
return s.syncUserAction(ctx, tenant, userId, consts.ImAdminCommandUserPasswordReset, payload)
}
func (s *sSysImUser) syncUserAction(ctx context.Context, tenant *entity.ImTenant, userId int64, eventType string, payload map[string]any) (*sysin.ImUserCommandModel, error) {
var out struct {
UserId int64 `json:"user_id"`
EventType string `json:"event_type"`
Status string `json:"status"`
Result map[string]any `json:"result"`
}
path := "/internal/admin-query/tenants/" + url.PathEscape(tenant.Code) + "/users/" + strconv.FormatInt(userId, 10) + "/action"
err := imadmin.DefaultClient(ctx).InternalPost(ctx, path, payload, &out)
if err != nil {
return nil, err
}
status := out.Status
if status == "" {
status = consts.ImAdminCommandStatusSucceeded
}
return &sysin.ImUserCommandModel{
CommandID: command.CommandID,
EventID: command.EventID,
Status: command.Status,
TenantID: command.TenantID,
TenantCode: command.TenantCode,
EventType: command.EventType,
IdempotencyKey: command.IdempotencyKey,
Status: status,
TenantID: tenant.Id,
TenantCode: tenant.Code,
EventType: eventType,
}, nil
}