使用線程本地變量存儲會員信息
線程本地變量
功能: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 應用程序中,每個請求由單獨的線程處理。
代碼的主要功能如下:
-
獲取當前會員信息:
getMember()方法用于獲取當前線程中存儲的會員登錄信息。
-
設置當前會員信息:
setMember(MemberLoginResp member)方法用于將會員信息存儲到當前線程中。
-
獲取會員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;
}
}
主要功能:
-
獲取 Token:
- 在每次請求處理之前,攔截器會從請求的
header中獲取token參數。 StrUtil.isNotBlank(token)用于檢查token是否非空且非空白字符。
- 在每次請求處理之前,攔截器會從請求的
-
解析 Token:
- 如果
token存在且有效,攔截器使用JwtUtil.getJSONObject(token)方法將token解析為JSONObject對象。 - 日志記錄:攔截器會將獲取到的
token以及解析后的會員信息記錄到日志中,便于跟蹤和調試。
- 如果
-
保存會員登錄信息:
- 解析后的
JSONObject對象會被轉換為MemberLoginResp對象。 - 調用
LoginMemberContext.setMember(member)方法,將該會員信息存儲到當前線程的上下文中,以便在后續請求處理中使用。
- 解析后的
-
繼續處理請求:
- 如果攔截器成功獲取并處理了
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"
);
}
}
代碼解析:
-
@Configuration注解:- 這個類被標注為一個配置類,意味著它定義了一些 Spring 容器的配置信息。
-
WebMvcConfigurer接口:SpringMvcConfig實現了WebMvcConfigurer接口,這是 Spring MVC 提供的一個擴展點,允許你自定義配置 MVC 的行為,而不需要繼承WebMvcConfigurerAdapter(在 Spring 5 中已廢棄)。
-
@Resource注解:@Resource注解用于自動注入MemberInterceptor實例。Spring 將自動掃描并注入已定義的MemberInterceptorBean。
-
addInterceptors方法:addInterceptors(InterceptorRegistry registry)方法用于注冊攔截器,并配置它應用于哪些請求路徑。
-
配置攔截器路徑:
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會員信息

浙公網安備 33010602011771號