使用线程本地变量存储会员信息

线程本地变量

功能:JWT单点登录时,后端通过前端请求header中的token解析出会员信息,这样就可以不通过前端传递就可以获取当前会员的信息。

线程本地变量
import com.guaigen.train.common.resp.MemberLoginResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginMemberContext {
    private static final Logger LOG = LoggerFactory.getLogger(LoginMemberContext.class);

    private static ThreadLocal<MemberLoginResp> member = new ThreadLocal<>();

    public static MemberLoginResp getMember() {
        return member.get();
    }

    public static void setMember(MemberLoginResp member) {
        LoginMemberContext.member.set(member);
    }

    public static Long getId() {
        try {
            return member.get().getId();
        } catch (Exception e) {
            LOG.info("获取会员登录信息异常:{}", e);
            throw e;
        }
    }
}

定义了一个名为 LoginMemberContext 的工具类,该类使用了 ThreadLocal 来存储和获取当前会员的登录信息(MemberLoginResp)。ThreadLocal 确保每个线程都有自己独立的 MemberLoginResp 实例,这在多线程环境中非常有用,例如在 Web 应用程序中,每个请求由单独的线程处理。

代码的主要功能如下:

  1. 获取当前会员信息

    • getMember() 方法用于获取当前线程中存储的会员登录信息。
  2. 设置当前会员信息

    • setMember(MemberLoginResp member) 方法用于将会员信息存储到当前线程中。
  3. 获取会员ID

    • getId() 方法用于获取当前线程中存储的会员ID。该方法包含了异常处理,如果在获取会员ID时发生异常,将记录错误信息并抛出异常。

这种设计确保了每个线程的登录上下文是独立的,不会相互影响,适合在需要线程隔离的场景中使用。

拦截器拦截线程本地变量

拦截器
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.guaigen.train.common.util.JwtUtil;
import com.guaigen.train.common.context.LoginMemberContext;
import com.guaigen.train.common.resp.MemberLoginResp;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印
 */
@Component
public class MemberInterceptor implements HandlerInterceptor {

    private static final Logger LOG = LoggerFactory.getLogger(MemberInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取header的token参数
        String token = request.getHeader("token");
        if (StrUtil.isNotBlank(token)) {
            LOG.info("获取会员登录token:{}", token);
            JSONObject loginMember = JwtUtil.getJSONObject(token);
            LOG.info("当前登录会员:{}", loginMember);
            MemberLoginResp member = JSONUtil.toBean(loginMember, MemberLoginResp.class);
            LoginMemberContext.setMember(member);
        }
        return true;
    }
}
基于 Spring 框架的拦截器 (`Interceptor`),名为,用于在每次 HTTP 请求处理之前执行一些操作,主要功能是验证用户的登录状态,并将登录信息保存到当前线程的上下文中。

主要功能:

  1. 获取 Token

    • 在每次请求处理之前,拦截器会从请求的 header 中获取 token 参数。
    • StrUtil.isNotBlank(token) 用于检查 token 是否非空且非空白字符。
  2. 解析 Token

    • 如果 token 存在且有效,拦截器使用 JwtUtil.getJSONObject(token) 方法将 token 解析为 JSONObject 对象。
    • 日志记录:拦截器会将获取到的 token 以及解析后的会员信息记录到日志中,便于跟踪和调试。
  3. 保存会员登录信息

    • 解析后的 JSONObject 对象会被转换为 MemberLoginResp 对象。
    • 调用 LoginMemberContext.setMember(member) 方法,将该会员信息存储到当前线程的上下文中,以便在后续请求处理中使用。
  4. 继续处理请求

    • 如果拦截器成功获取并处理了 token,会返回 true,表示可以继续处理该请求。否则,请求可能会被阻止或进一步处理。

    这个拦截器通常用于登录验证,确保请求中包含有效的 token,并将登录信息加载到上下文中供后续使用。这样的设计在需要用户认证和权限管理的场景中非常常见。

可能的扩展:

  • 权限校验:除了登录验证,还可以扩展该拦截器,添加权限校验功能,确保用户有权访问某些资源或执行特定操作。
  • 日志记录:记录更多的请求细节,如请求路径、请求参数等,以便进行审计和问题排查。

配置文件

config
import com.guaigen.train.common.interceptor.MemberInterceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    @Resource
    MemberInterceptor memberInterceptor;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(memberInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/member/hello",
                        "/member/member/sendCode",
                        "/member/member/login"
                );
    }
}
Spring MVC 配置类,用于注册并配置拦截器 `MemberInterceptor`。

代码解析:

  1. @Configuration 注解

    • 这个类被标注为一个配置类,意味着它定义了一些 Spring 容器的配置信息。
  2. WebMvcConfigurer 接口

    • SpringMvcConfig 实现了 WebMvcConfigurer 接口,这是 Spring MVC 提供的一个扩展点,允许你自定义配置 MVC 的行为,而不需要继承 WebMvcConfigurerAdapter(在 Spring 5 中已废弃)。
  3. @Resource 注解

    • @Resource 注解用于自动注入 MemberInterceptor 实例。Spring 将自动扫描并注入已定义的 MemberInterceptor Bean。
  4. addInterceptors 方法

    • addInterceptors(InterceptorRegistry registry) 方法用于注册拦截器,并配置它应用于哪些请求路径。
  5. 配置拦截器路径

    • registry.addInterceptor(memberInterceptor)MemberInterceptor 添加到拦截器链中。
    • .addPathPatterns("/**") 指定拦截器应用于所有路径(/** 表示匹配所有路径)。
    • .excludePathPatterns("/member/hello", "/member/member/sendCode", "/member/member/login") 指定某些路径不被拦截器拦截,这通常是登录、验证码发送等公共接口。

功能概述:

  • 全局拦截

    • 这个配置类将 MemberInterceptor 应用于所有请求路径,但排除了一些指定的公共路径。这意味着除了登录和发送验证码的路径外,所有其他路径都将经过 MemberInterceptor 的处理。
  • 典型场景

    • 这种配置方式通常用于保护应用的业务逻辑部分,确保只有通过认证的用户才能访问特定资源。同时,也保留了一些不需要认证的公共接口供外部使用。

可扩展性:

  • 根据需要,增加或减少 excludePathPatterns 中的路径,以控制哪些请求路径不需要经过拦截器。
  • 如果有多个拦截器,也可以通过 registry.addInterceptor(...) 继续添加和配置其他拦截器。

后端使用

后端Service调用
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import com.guaigen.train.common.context.LoginMemberContext;
import com.guaigen.train.common.util.SnowUtil;
import com.guaigen.train.member.domain.Passenger;
import com.guaigen.train.member.mapper.PassengerMapper;
import com.guaigen.train.member.req.PassengerSaveReq;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class PassengerService {

    @Resource
    private PassengerMapper passengerMapper;

    public void save(PassengerSaveReq req) {
        DateTime now = DateTime.now();
        Passenger passenger = BeanUtil.copyProperties(req, Passenger.class);
        passenger.setMemberId(LoginMemberContext.getId());
        passenger.setId(SnowUtil.getSnowflakeNestId());
        passenger.setCreatedTime(now);
        passenger.setModifiedTime(now);
        passengerMapper.insert(passenger);
    }
}

其中passenger.setMemberId(LoginMemberContext.getId());就是通过线程本地变量获取memberId会员信息

posted @ 2024-09-03 17:52  鱼摆摆不摆  阅读(10)  评论(0编辑  收藏  举报