From cde4480b51a65346bd60d19e903015eebbc7aa91 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Fri, 28 Aug 2026 14:18:27 +0800 Subject: [PATCH] feat: publish password reset via kafka --- server/api/admin/imuser/im_user.go | 4 +- server/internal/consts/im_kafka.go | 1 + .../internal/controller/admin/sys/im_user.go | 7 +- server/internal/logic/sys/im_user.go | 66 +++++++++++++++---- server/internal/service/sys.go | 2 +- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/server/api/admin/imuser/im_user.go b/server/api/admin/imuser/im_user.go index 428d056..7db8213 100644 --- a/server/api/admin/imuser/im_user.go +++ b/server/api/admin/imuser/im_user.go @@ -38,7 +38,9 @@ type ResetPasswordReq struct { sysin.ImUserPasswordResetInp } -type ResetPasswordRes struct{} +type ResetPasswordRes struct { + *sysin.ImUserCommandModel +} type RevokeSessionsReq struct { g.Meta `path:"/imUser/revokeSessions" method:"post" tags:"IM用户" summary:"撤销IM用户会话"` diff --git a/server/internal/consts/im_kafka.go b/server/internal/consts/im_kafka.go index 474b5c9..4d26cfa 100644 --- a/server/internal/consts/im_kafka.go +++ b/server/internal/consts/im_kafka.go @@ -46,6 +46,7 @@ const ( ImAdminCommandUserLogoutAll = "user.logout_all" ImAdminCommandUserCancel = "user.account.cancel" ImAdminCommandUserDelete = "user.account.delete" + ImAdminCommandUserPasswordReset = "user.password.reset" ImAdminCommandKafkaDeadReplay = "kafka.dead_letter.replay" ImAdminCommandKafkaDeadIgnore = "kafka.dead_letter.ignore" ImAdminCommandKafkaDeadResolve = "kafka.dead_letter.resolve" diff --git a/server/internal/controller/admin/sys/im_user.go b/server/internal/controller/admin/sys/im_user.go index 88a1235..fb5d61d 100644 --- a/server/internal/controller/admin/sys/im_user.go +++ b/server/internal/controller/admin/sys/im_user.go @@ -48,7 +48,12 @@ func (c *cImUser) View(ctx context.Context, req *imuser.ViewReq) (res *imuser.Vi } func (c *cImUser) ResetPassword(ctx context.Context, req *imuser.ResetPasswordReq) (res *imuser.ResetPasswordRes, err error) { - err = service.SysImUser().ResetPassword(ctx, &req.ImUserPasswordResetInp) + data, err := service.SysImUser().ResetPassword(ctx, &req.ImUserPasswordResetInp) + if err != nil { + return + } + res = new(imuser.ResetPasswordRes) + res.ImUserCommandModel = data return } diff --git a/server/internal/logic/sys/im_user.go b/server/internal/logic/sys/im_user.go index a7c4365..9c42657 100644 --- a/server/internal/logic/sys/im_user.go +++ b/server/internal/logic/sys/im_user.go @@ -8,6 +8,7 @@ package sys import ( "context" "crypto/sha256" + "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -15,6 +16,7 @@ import ( "hotgo/internal/library/contexts" "hotgo/internal/library/imkafka" "hotgo/internal/library/imtenant" + "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "net/url" @@ -209,29 +211,25 @@ func (s *sSysImUser) viewFields() []interface{} { } } -func (s *sSysImUser) ResetPassword(ctx context.Context, in *sysin.ImUserPasswordResetInp) (err error) { +func (s *sSysImUser) ResetPassword(ctx context.Context, in *sysin.ImUserPasswordResetInp) (res *sysin.ImUserCommandModel, err error) { if err = in.Filter(ctx); err != nil { - return + return nil, err } - scope, err := resolveImAdminScope(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { - return err + return nil, err } userId, err := sysin.ParseOptionalInt64(in.UserId) if err != nil { - return err + return nil, err } - db, _, err := imtenant.DB(ctx, scope.TenantId) - if err != nil { - return err - } - err = resetIMUserPassword(ctx, db, scope, userId, in.NewPassword) + command, err := s.publishPasswordResetCommand(ctx, tenant, userId, in.NewPassword) result := "success" if err != nil { result = "failed" } - _ = writeImAdminAudit(ctx, scope.TenantId, "user_password_reset", "user", userId, result, nil) - return err + _ = writeImAdminAudit(ctx, tenant.Id, "user_password_reset", "user", userId, result, nil) + return command, err } func (s *sSysImUser) RevokeSessions(ctx context.Context, in *sysin.ImUserRevokeSessionsInp) (err error) { @@ -391,6 +389,50 @@ func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImU }, 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) + 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), + } + 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), + }) + 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 updateIMUserStatus(ctx context.Context, db gdb.DB, scope imAdminScope, userId int64, state int) error { securityId, err := imtenant.NextSnowflakeID(ctx) if err != nil { diff --git a/server/internal/service/sys.go b/server/internal/service/sys.go index 88804fb..b79497d 100644 --- a/server/internal/service/sys.go +++ b/server/internal/service/sys.go @@ -219,7 +219,7 @@ type ( // View 获取IM用户指定信息 View(ctx context.Context, in *sysin.ImUserViewInp) (res *sysin.ImUserViewModel, err error) // ResetPassword 管理员重置IM用户密码 - ResetPassword(ctx context.Context, in *sysin.ImUserPasswordResetInp) (err error) + ResetPassword(ctx context.Context, in *sysin.ImUserPasswordResetInp) (res *sysin.ImUserCommandModel, err error) // RevokeSessions 撤销IM用户会话 RevokeSessions(ctx context.Context, in *sysin.ImUserRevokeSessionsInp) (err error) // Status 更新IM用户状态