以便分别做相应的处理
token.private-key=hello-daijiyong
#token25分钟后自动刷新
token.expires.young=2500000
#token30分钟后过期
token.expires.old=3000000
设置token拦截处理器
将token放到header中,针对每一次请求都进行token验证处理
如果token不存在或者错误,则抛出异常
@Slf4j
@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {
/**
* 权限认证的拦截操作.
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws TokenAuthException {
log.info("=======进入拦截器========");
// 如果不是映射到方法直接通过,可以访问资源.
if (!(object instanceof HandlerMethod)) {
return true;
}
//为空就返回错误
String token = httpServletRequest.getHeader(TokenUtil.REQUEST_HEADER_TOKEN_KEY);
if (null == token || "".equals(token.trim())) {
throw new TokenAuthException(ReturnCode.INVALID_TOKEN);
}
TokenVo tokenVo = TokenUtil.parseToken(token);
long timeOfUse = System.currentTimeMillis() - tokenVo.getTimestamp();
//1.判断 token 是否过期
//年轻 token
/*if (timeOfUse < TokenUtil.youngToken) {
log.info("年轻 token");
}*/
//老年 token 就刷新 token
if (timeOfUse >= TokenUtil.youngToken && timeOfUse < TokenUtil.oldToken) {
TokenUtil.getToken(tokenVo);
httpServletResponse.setHeader(TokenUtil.REQUEST_HEADER_TOKEN_KEY, tokenVo.getToken());
httpServletResponse.setHeader(TokenUtil.REQUEST_HEADER_EXPIRES_KEY, String.valueOf(tokenVo.getTimestamp() + TokenUtil.oldToken));
}
//过期 token 就返回 token 无效.
else {
throw new TokenAuthException(ReturnCode.INVALID_TOKEN);
}
return false;
}
}
配置拦截器内容,放行登录和测试接口等
@Configuration
public class AuthWebMvcConfigurer implements WebMvcConfigurer {
@Resource
AuthHandlerInterceptor authHandlerInterceptor;
private final String[] excludePathPatters = new String[]{"/api/wechat/token/getToken", "/api/test/**"};
/**
* 给除了 excludePathPatters 配置的接口都配置拦截器,拦截转向到 authHandlerInterceptor
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authHandlerInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePathPatters);
}
}
token获取和更新接口
@Slf4j
@RestController
@RequestMapping("/api/wechat/token")
public class TokenController {
@RequestMapping(path = "/getToken", method = RequestMethod.POST)
public TokenDataDto getToken(TokenVo tokenVo) throws TokenAuthException {
log.info("请求token参数:{}", tokenVo);
TokenUtil.getToken(tokenVo);
return new TokenDataDto(tokenVo);
}
@RequestMapping(path = "/refreshToken", method = RequestMethod.POST)
public TokenDataDto refreshToken(HttpServletRequest request) throws TokenAuthException {
String token = request.getHeader(TokenUtil.REQUEST_HEADER_TOKEN_KEY);
if (StringUtils.isBlank(token)) {
throw new TokenAuthException(ReturnCode.INVALID_TOKEN);
}
TokenVo tokenVo = TokenUtil.parseToken(token);
long timeOfUse = System.currentTimeMillis() - tokenVo.getTimestamp();
//判断 token 是否过期
if (timeOfUse > TokenUtil.oldToken) {
throw new TokenAuthException(ReturnCode.INVALID_TOKEN);
}
TokenUtil.getToken(tokenVo);
return new TokenDataDto(tokenVo);
}
}
## 全局异常捕获
根据自定义异常类型,进行全局异常捕获
定义接口处理异常类型
public class ResponseException extends Exception {
static final long serialVersionUID = 1L;
private final String status;
public ResponseException(String status) {
super();
this.status = status;
}
public ResponseException(String message, String status) {
super(message);
this.status = status;
}
public ResponseException(ReturnCode returnCode) {
super(returnCode.getMessage());
this.status = returnCode.getStatus();
}
public String getStatus() {
return status;
}
}
通过@RestControllerAdvice注解捕捉特定异常类型
比如token异常、接口处理异常等
并做相应的处理
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
/**
* 默认全局异常处理。
*
* @param e the e
* @return ResultData
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public ResultData<String> exception(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return ResultData.fail(ReturnCode.FAIL.getStatus(), e.getMessage());
}
/**
* 默认全局异常处理。
*
* @param e the e
* @return ResultData
*/
@ExceptionHandler(ResponseException.class)
@ResponseStatus(HttpStatus.OK)
public ResultData<String> runtimeException(ResponseException e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return ResultData.fail(e.getStatus(), e.getMessage());
}
/**
* token异常处理。
*
* @param e the e
* @return ResultData
*/
@ExceptionHandler(TokenAuthException.class)
@ResponseStatus(HttpStatus.OK)
public ResultData<String> tokenAuthException(TokenAuthException e) {
log.error("token异常信息 ex={}", e.getMessage(), e);
return ResultData.fail(e.getStatus(), e.getMessage());
}
}
可通过以下测试接口测试一下
@RestController
@RequestMapping("/api/test")
public class TestController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private TestService testService;
@RequestMapping("/success")
public String test() {
logger.info("数据接口测试成功!!!");
testService.test();
return "数据测试成功";
}
@RequestMapping("/fail")
public void fail() {
throw new RuntimeException("异常测试");
}
}
## 统一接口返回实体封装
传统的处理方式我们需要定义一个如下的实体类
@Data
public class ResultData<T> {
private String status;
private String message;
private T data;
private long timestamp;
//省略
}
然后在每一个接口返回的地方new一个新对象
并将数据实体set到data中
很是繁琐且不优雅
下面实现接口返回实体自动封装的功能
比如定义的token获取和更新接口,只需返回数据实体即可
会自动封装成特定的数据格式
@RestController
@RequestMapping("/api/test")
public class TestController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private TestService testService;
@RequestMapping("/success")
public String test() {
logger.info("数据接口测试成功!!!");
testService.test();
return "数据测试成功";
}
@RequestMapping("/fail")
public void fail() {
throw new RuntimeException("异常测试");
}
@RequestMapping(path = "/refreshToken", method = RequestMethod.POST)
public TokenDataDto refreshToken(HttpServletRequest request) throws TokenAuthException {
String token = request.getHeader(TokenUtil.REQUEST_HEADER_TOKEN_KEY);
if (StringUtils.isBlank(token)) {
throw new TokenAuthException(ReturnCode.INVALID_TOKEN);
}
TokenVo tokenVo = TokenUtil.parseToken(token);
long timeOfUse = System.currentTimeMillis() - tokenVo.getTimestamp();
//判断 token 是否过期
if (timeOfUse > TokenUtil.oldToken) {
throw new TokenAuthException(ReturnCode.INVALID_TOKEN);
}
TokenUtil.getToken(tokenVo);
return new TokenDataDto(tokenVo);
}
}
通过@RestControllerAdvice注解捕捉所有接口返回结果
并对返回结果进行统一的封装处理
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Resource
ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (o instanceof String) {
try {
return objectMapper.writeValueAsString(ResultData.success(o));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
// 如果已经封装了返回实体,则不再进行封装
if (o instanceof ResultData) {
return o;
}
return ResultData.success(o);
}
}
通过以上设置,可以避免繁琐的操作,全局统一封装返回实体
{
"status": "000000",
"message": "操作成功!",
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImRhaWppeW9uZyIsInVzZXJOYW1lIjoiZGFpaml5b25nIiwidGltZXN0YW1wIjoxNjMyNTc3OTI5MDc0fQ.gdrwQmyMStNnCxUqPYPay_igjdPBNmbvuUIoavYnbhM",
"expires": 1632580929074
},
"timestamp": 1632577929076
}
## 后续安排
逐步完善框架功能,集成反向工程功能、集成分页插件、接口文档、代码优化等
文/戴先生@2021年10月05日
---end---