diff --git a/controller/user.go b/controller/user.go index a12d5c66..0e4786fb 100644 --- a/controller/user.go +++ b/controller/user.go @@ -891,6 +891,11 @@ func ManageUser(c *gin.Context) { }) return } + // 删除用户后,强制清理 Redis 中所有该用户令牌的缓存, + // 避免已缓存的令牌在 TTL 过期前仍能通过 TokenAuth 校验。 + if err := model.InvalidateUserTokensCache(user.Id); err != nil { + common.SysLog(fmt.Sprintf("failed to invalidate tokens cache for user %d: %s", user.Id, err.Error())) + } case "promote": if myRole != common.RoleRootUser { common.ApiErrorI18n(c, i18n.MsgUserAdminCannotPromote) @@ -959,6 +964,18 @@ func ManageUser(c *gin.Context) { common.ApiError(c, err) return } + // 禁用 / 角色调整后,强制失效用户缓存与其全部令牌缓存, + // 避免在 Redis TTL 过期前仍使用旧状态(尤其是禁用后仍可发起请求的问题)。 + // InvalidateUserCache 会让下一次 GetUserCache 从数据库重新加载, + // InvalidateUserTokensCache 则确保令牌侧的缓存也同步刷新。 + if req.Action == "disable" || req.Action == "promote" || req.Action == "demote" { + if err := model.InvalidateUserCache(user.Id); err != nil { + common.SysLog(fmt.Sprintf("failed to invalidate user cache for user %d: %s", user.Id, err.Error())) + } + if err := model.InvalidateUserTokensCache(user.Id); err != nil { + common.SysLog(fmt.Sprintf("failed to invalidate tokens cache for user %d: %s", user.Id, err.Error())) + } + } clearUser := model.User{ Role: user.Role, Status: user.Status, diff --git a/model/token.go b/model/token.go index 8cfcd618..0529e2c7 100644 --- a/model/token.go +++ b/model/token.go @@ -480,3 +480,32 @@ func GetTokenKeysByIds(ids []int, userId int) ([]Token, error) { Find(&tokens).Error return tokens, err } + +// InvalidateUserTokensCache 清理指定用户所有令牌在 Redis 中的缓存, +// 配合 InvalidateUserCache 使用,可在用户被禁用/删除时立即阻断其令牌的请求。 +// 下一次请求将从数据库重新加载令牌及用户状态,从而立即识别出被禁用的用户。 +func InvalidateUserTokensCache(userId int) error { + if !common.RedisEnabled { + return nil + } + if userId <= 0 { + return errors.New("userId 无效") + } + var tokens []Token + if err := DB.Unscoped(). + Select("id", commonKeyCol). + Where("user_id = ?", userId). + Find(&tokens).Error; err != nil { + return err + } + var firstErr error + for _, t := range tokens { + if t.Key == "" { + continue + } + if err := cacheDeleteToken(t.Key); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} diff --git a/model/user_cache.go b/model/user_cache.go index 2ba1f18e..80d0264f 100644 --- a/model/user_cache.go +++ b/model/user_cache.go @@ -57,6 +57,12 @@ func invalidateUserCache(userId int) error { return common.RedisDelKey(getUserCacheKey(userId)) } +// InvalidateUserCache is the exported version of invalidateUserCache. +// 供 controller 等上层包在用户状态变更(如禁用、删除、角色变更)后主动清理缓存。 +func InvalidateUserCache(userId int) error { + return invalidateUserCache(userId) +} + // updateUserCache updates all user cache fields using hash func updateUserCache(user User) error { if !common.RedisEnabled {