package auth import ( "context" "errors" "fmt" "perms-system-server/internal/consts" "perms-system-server/internal/middleware" userModel "perms-system-server/internal/model/user" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/types" "perms-system-server/internal/util" "github.com/zeromicro/go-zero/core/limit" "github.com/zeromicro/go-zero/core/logx" "golang.org/x/crypto/bcrypt" ) type ChangePasswordLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewChangePasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ChangePasswordLogic { return &ChangePasswordLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // ChangePassword 修改密码。已登录用户验证原密码后设置新密码,同时递增 tokenVersion 使所有已签发令牌失效,强制重新登录。 func (l *ChangePasswordLogic) ChangePassword(req *types.ChangePasswordReq) error { if msg := util.ValidatePassword(req.NewPassword); msg != "" { return response.ErrBadRequest(msg) } userId := middleware.GetUserId(l.ctx) if l.svcCtx.TokenOpLimiter != nil { code, _ := l.svcCtx.TokenOpLimiter.Take(fmt.Sprintf("chpwd:%d", userId)) if code == limit.OverQuota { return response.ErrTooManyRequests("操作过于频繁,请稍后再试") } } user, err := l.svcCtx.SysUserModel.FindOne(l.ctx, userId) if err != nil { return response.ErrNotFound("用户不存在") } if user.Status != consts.StatusEnabled { return response.ErrForbidden("账号已被冻结") } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil { logx.WithContext(l.ctx).Infof("change-password old-password mismatch userId=%d", userId) return response.ErrBadRequest("原密码错误") } if req.OldPassword == req.NewPassword { return response.ErrBadRequest("新密码不能与原密码相同") } hashed, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { return err } if err := l.svcCtx.SysUserModel.UpdatePassword(l.ctx, userId, string(hashed), consts.MustChangePasswordNo); err != nil { // 审计 M-R10-4:与 UpdateUserLogic / UpdateRoleLogic / UpdateUserStatusLogic 口径对齐, // 把乐观锁失败显式映射成 409,避免 raw error 被 rest 框架兜成 500、前端错把"并发冲突" // 当作系统故障处理,告警看板也不会把这类事件归到 5xx 噪声池。 if errors.Is(err, userModel.ErrUpdateConflict) { return response.ErrConflict("密码已被其他会话修改,请刷新后重试") } return err } l.svcCtx.UserDetailsLoader.Clean(l.ctx, userId) return nil }