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
+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
}