token的处理和统一异常处理
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()); } }