MeteorCat / 全局动态数据

Created Sat, 28 Dec 2024 14:23:01 +0800 Modified Wed, 29 Oct 2025 23:25:00 +0800
751 Words

全局动态数据

这里主要涉及到 SpringBoot 授权对话基于认证规范该怎么全局获取到玩家信息实体.

通用HTTP授权规范

一般授权完成授权之后会以 Header 形式追加以下格式结构:

Authorization: <type> <credentials>

type 字段包含有:

  • Basic: 基本明文账号密码之后base64编码
  • Bearer: OAuth|JWT 授权机制, 目前最常见
  • ….., 其他可以自行了解

可以按照自身需求构建 JWT 或者直接采用唯一哈希对应放置在 Redis 关联即可, 用户重新登录会去 Redis 注销上一次的 token 映射.

言归正传就是当登录完全通过 HandlerInterceptor 检索出来 Token 在数据库|Redis查询到对应数据实体之后怎么让全局被访问.

网上很多说采用 ThreadLocal 直接线程本地变量保存, 这样其实最大问题是内部出现 Exception 会导致内存没办法回收, 最后到达一定量及就会出现大量内存泄露.

另外设置和释放都需要手动进行分配和注销, 比如在 HandlerInterceptor 当中:

public class HandlerInterceptorConfigurer implements HandlerInterceptor {

    private static final ThreadLocal<UserModel> USER = new ThreadLocal<>();

    /**
     * 请求拦截
     */
    @Override
    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
        User.set(new UserModel()); // 读取加载线程本地变量
    }

    /**
     * 响应拦截
     */
    @Override
    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) throws Exception {
        User.remove();// 手动释放
    }
}

只要内部出现 Exception 级别, 那么就无法保证 afterCompletion 会被拦截到, 所以就开始出现内存泄露.

SpringBoot 则提供 ThreadLocal 做底层的另外回收方案: RequestContextHolder.

RequestContextHolder 是内部与请求上下文绑定, 也就是每次访问都会由底层生成新的 RequestContextHolder 实例, 请求结束之后就会自动开始回收该实例数据对象, 好处就是直接底层帮你维护请求过程的全局变量, 坏处是不能跨线程和依赖 Spring.

那么之后按照自己要求定义请求过程的变量读取器:

/**
 * 静态获取授权对象, 利用 Spring 自带的 RequestContextHolder 构建会话过程的全局玩家数据
 */
public class AuthorizedService {

    private static final String AuthorizedAttributeUser = "AuthorizedUser";

    private static final String AuthorizedAttributeToken = "AuthorizedToken";

    public static void setUser(User user) {
        RequestContextHolder.currentRequestAttributes().setAttribute(
                AuthorizedAttributeUser,
                user,
                RequestAttributes.SCOPE_REQUEST
        );
    }

    public static User getUser() {
        Object handler = RequestContextHolder.currentRequestAttributes().getAttribute(
                AuthorizedAttributeUser,
                RequestAttributes.SCOPE_REQUEST
        );
        if (handler instanceof User) {
            return (User) handler;
        } else {
            return null;
        }
    }

    public static void removeUser() {
        RequestContextHolder.currentRequestAttributes().removeAttribute(
                AuthorizedAttributeUser,
                RequestAttributes.SCOPE_REQUEST
        );
    }

    public static void setToken(String token) {
        RequestContextHolder.currentRequestAttributes().setAttribute(
                AuthorizedAttributeToken,
                token,
                RequestAttributes.SCOPE_REQUEST
        );
    }

    public static String getToken() {
        Object handler = RequestContextHolder.currentRequestAttributes().getAttribute(
                AuthorizedAttributeToken,
                RequestAttributes.SCOPE_REQUEST
        );
        if (handler instanceof String) {
            return (String) handler;
        } else {
            return null;
        }
    }

    public static void removeToken() {
        RequestContextHolder.currentRequestAttributes().removeAttribute(
                AuthorizedAttributeToken,
                RequestAttributes.SCOPE_REQUEST
        );
    }
}

这样就能全局保存好对应玩家登录实体数据, 不需要访问需要玩家实体都从数据库当中获取检索.