logoutLogic.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. // Code scaffolded by goctl. Safe to edit.
  2. // goctl 1.10.0
  3. package auth
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "perms-system-server/internal/loaders"
  9. "perms-system-server/internal/middleware"
  10. userModel "perms-system-server/internal/model/user"
  11. "perms-system-server/internal/response"
  12. "perms-system-server/internal/svc"
  13. "github.com/zeromicro/go-zero/core/limit"
  14. "github.com/zeromicro/go-zero/core/logx"
  15. )
  16. type LogoutLogic struct {
  17. logx.Logger
  18. ctx context.Context
  19. svcCtx *svc.ServiceContext
  20. }
  21. func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
  22. return &LogoutLogic{
  23. Logger: logx.WithContext(ctx),
  24. ctx: ctx,
  25. svcCtx: svcCtx,
  26. }
  27. }
  28. // Logout 用户注销。递增当前用户的 tokenVersion 使所有已签发的 access/refresh 令牌立即失效,并清除用户缓存。
  29. func (l *LogoutLogic) Logout() error {
  30. // 审计 M-R11-2:从 middleware 的 UserDetails 直接取 userId/username,
  31. // 不再到 IncrementTokenVersion 内部再 FindOne 一次仅为构造缓存键。
  32. ud := middleware.GetUserDetails(l.ctx)
  33. if ud == nil || ud.UserId == 0 {
  34. return response.ErrUnauthorized("未登录")
  35. }
  36. userId := ud.UserId
  37. if l.svcCtx.TokenOpLimiter != nil {
  38. code, _ := l.svcCtx.TokenOpLimiter.Take(fmt.Sprintf("logout:%d", userId))
  39. if code == limit.OverQuota {
  40. return response.ErrTooManyRequests("操作过于频繁,请稍后再试")
  41. }
  42. }
  43. if _, err := l.svcCtx.SysUserModel.IncrementTokenVersion(l.ctx, userId, ud.Username); err != nil {
  44. // 审计 L-R10-3:IncrementTokenVersion 在目标用户已被并发删除时会返 ErrUpdateConflict。
  45. // Logout 的语义目标本就是"让该账号的旧令牌立即失效",用户已经消失等同语义已达成,
  46. // 按幂等成功处理并继续清缓存,不要让一次正常的注销因为极罕见的删号竞态回 500。
  47. if !errors.Is(err, userModel.ErrUpdateConflict) {
  48. return err
  49. }
  50. logx.WithContext(l.ctx).Infof("logout on already-deleted user userId=%d, treated as idempotent success", userId)
  51. }
  52. // 审计 L-R13-5 方案 A:Logout 的目的正是让旧令牌立即失效;缓存失效不能因为 HTTP
  53. // 请求 ctx 被客户端取消而丢失。detached ctx 确保 IncrementTokenVersion 提交后 UD
  54. // 即使在 5 分钟 TTL 内也能被主动失效。
  55. cleanCtx, cancel := loaders.DetachCacheCleanCtx(l.ctx)
  56. defer cancel()
  57. l.svcCtx.UserDetailsLoader.Clean(cleanCtx, userId)
  58. return nil
  59. }