執行緒本地變數
功能: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 將自動掃描並注入已定義的MemberInterceptor
Bean。
-
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會員資訊