3.1 問題分析
3.1.1 問題
token 為了測試方便增大存活時長,但是之后token能獲去但總是不對,因為數據越界,變為-1,導致本來想增大存活時長,但卻早早就使得token過期了
每一個控制方法中都需要解析token , 獲取當前用戶id , 代碼重復度比較高
-
重復性的登錄驗證
-
繁瑣的token獲取及解析
3.1.2 解決方案
基於ThreadLocal + 攔截器的形式統一處理
攔截器(Interceptor)
-
是一種動態攔截方法調用的機制;
-
類似於Servlet 開發中的過濾器Filter,用於對處理器進行前置處理和后置處理。
ThreadLocal
-
線程內部的存儲類,賦予了線程存儲數據的能力。
-
線程內調用的方法都可以從ThreadLocal中獲取同一個對象。
-
多個線程中ThreadLocal數據相互隔離
Threadlocal使用方法很簡單
ThreadLocal<T> threadLocal = new ThreadLocal<T>(); threadLocal.set() //將數據綁定到當前線程 threadLocal.get() //從當前線程中獲取數據
定義攔截器
定義攔截器,在前置攔截方法preHandle中解析token並驗證有效性,如果失效返回狀態碼401。如果有效,解析User對象,存入ThreadLocal中
package com.i.server.interceptor; /** * @author Administrator */ public class TokenInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1、獲取請求頭 String token = request.getHeader("Authorization"); //2、使用工具類,判斷token是否有效 boolean verifyToken = JwtUtils.verifyToken(token); //3、如果token失效,返回狀態碼401,攔截 if(!verifyToken) { response.setStatus(401); return false; } //4、如果token正常可用,放行 //解析token,獲取id和手機號碼, Claims claims = JwtUtils.getClaims(token); String mobile = (String) claims.get("mobile"); Integer id = (Integer) claims.get("id"); //構造User對象,存入Threadlocal User user = new User(); user.setId(Long.valueOf(id)); user.setMobile(mobile); UserHolder.set(user); return true; } //清空 解決ThreadLocal中數據越存越多導致內存溢出問題 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.remove(); } }
攔截器需要注冊到MVC容器中
/** * @author Administrator */ @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TokenInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/user/login", "/user/loginVerification"); } }
定義ThreadLocal工具類,僅需要調用set方法即可將數據存入ThreadLocal中
package com.i.server.interceptor; import com.i.model.domain.User; /** * @author Administrator */ public class UserHolder { private static ThreadLocal<User> tl = new ThreadLocal<User>(); /** * 保存數據到線程 */ public static void set(User user) { tl.set(user); } /** * 獲取線程中的用戶信息 */ public static User get() { return tl.get(); } /** * 從當前線程,獲取用戶對象的id */ public static Long getUserId() { if (tl.get() == null) { return null; } return tl.get().getId(); } /** * 從當前線程,獲取用戶對象的手機號碼 */ public static String getMobile() { if (tl.get() == null) { return null; } return tl.get().getMobile(); } /** * 移除線程中數據 */ public static void remove() { tl.remove(); } }
修改控制器方法, 所有需要用到
userId
都可以直接從線程中獲取
@PostMapping(path = "/loginReginfo") public ResponseEntity loginReginfo(@RequestBody UserInfo userInfo) { //1. 校驗token Long userId = UserHolder.getUserId(); //2. 保存用戶信息 userInfo.setId(userId); userInfoService.save(userInfo); //3. 返回數據 return ResponseEntity.ok(null); }
-
冗余代碼多,影響代碼可讀性
-
異常處理和業務代碼耦合
SpringMVC提供了一套解決全局異常的處理方案,可以在代碼無侵入的前提下完成異常處理。遵循逐層拋出,異常處理器統一處理的思路
(面向切面編程 aop)
還有一類是可預知的錯誤,如圖片不合法,驗證碼錯誤等等。這類錯誤也可以理解為業務異常,可以通過自定義異常類來處理;
為了方便操作,將一些常見的業務錯誤封裝到ErrorResult對象中
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class ErrorResult { private String errCode = "999999"; private String errMessage; public static ErrorResult error() { return ErrorResult.builder().errCode("999999").errMessage("系統異常稍后再試").build(); } public static ErrorResult fail() { return ErrorResult.builder().errCode("000001").errMessage("發送驗證碼失敗").build(); } public static ErrorResult loginError() { return ErrorResult.builder().errCode("000002").errMessage("驗證碼失效").build(); } public static ErrorResult faceError() { return ErrorResult.builder().errCode("000003").errMessage("圖片非人像,請重新上傳!").build(); } public static ErrorResult mobileError() { return ErrorResult.builder().errCode("000004").errMessage("手機號碼已注冊").build(); } public static ErrorResult contentError() { return ErrorResult.builder().errCode("000005").errMessage("動態內容為空").build(); } public static ErrorResult likeError() { return ErrorResult.builder().errCode("000006").errMessage("用戶已點贊").build(); } public static ErrorResult disLikeError() { return ErrorResult.builder().errCode("000007").errMessage("用戶未點贊").build(); } public static ErrorResult loveError() { return ErrorResult.builder().errCode("000008").errMessage("用戶已喜歡").build(); } public static ErrorResult disloveError() { return ErrorResult.builder().errCode("000009").errMessage("用戶未喜歡").build(); } }
自定義業務異常類,針對業務錯誤之間拋出業務異常即可
/** * 自定義異常類 */ @Data public class BusinessException extends RuntimeException { private ErrorResult errorResult; public BusinessException(ErrorResult errorResult) { super(errorResult.getErrMessage()); this.errorResult = errorResult; } }
/** * 自定義統一異常處理 * 1、通過注解,聲明異常處理類 * 2、編寫方法,在方法內部處理異常,構造響應數據 * 3、方法上編寫注解,指定此方法可以處理的異常類型 */ @ControllerAdvice public class ExceptionAdvice { //處理業務異常 @ExceptionHandler(BusinessException.class) public ResponseEntity handlerException(BusinessException be) { be.printStackTrace(); ErrorResult errorResult = be.getErrorResult(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult); } //處理不可預知的異常 @ExceptionHandler(Exception.class) public ResponseEntity handlerException1(Exception be) { be.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResult.error()); } }