用途
- 日常项目中。
- 1、我们的记录请求日志,或者在返回的时候记录请求日志。
- 2、我们的公共校验、比如签名校验、密钥校验、过期时间检验等
- 3、统一异常处理等。
@ControllerAdvice与RequestBodyAdviceAdapter
-
@ControllerAdvice注解可以扫描针对Controller层的扩展组件。通过@Order注解可以使其支持顺序加载。
-
RequestBodyAdviceAdapter是RequestBodyAdvice适配器类,可以方便的扩展所需要的方法。
-
RequestBodyAdvice功能如下:
-
允许在请求消息体在被读取及调用convert转换成实体之前做一些个人化操作,作用于含有@RequestBody注解的请求。实现此接口的类,需要在RequestMappingHandlerAdapter中配置或通过@ControllerAdvice注解配置。
代码如下
@Slf4j
public abstract class AbstractApiAdvice extends RequestBodyAdviceAdapter {
@Autowired
private AuthConfig authConfig;
protected static final String HEAD_SIGN_PARAMETER = "sign";
protected static final String HEAD_TIMESTAMP_PARAMETER = "timestamp";
protected static final String HEAD_APP_KEY_PARAMETER = "appKey";
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@ResponseBody
@ExceptionHandler
public RestResponse<?> exceptionHandle(BusinessException e) {
return new RestResponse<>(e.getCode(), e.getMessage());
}
@ResponseBody
@ExceptionHandler
public RestResponse<?> exceptionHandle(Exception e) {
log.error("发生未知异常", e);
return new RestResponse<>(ErrorCode.OTHER_ERROR, e.getMessage());
}
@SuppressWarnings("NullableProblems")
@Override
public boolean supports(MethodParameter methodParameter
, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SuppressWarnings("NullableProblems")
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter
, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
HttpHeaders headers = inputMessage.getHeaders();
final String sign = headers.getFirst(HEAD_SIGN_PARAMETER);
final String timestamp = headers.getFirst(HEAD_TIMESTAMP_PARAMETER);
// 字段缺失校验
if (StrUtil.isEmpty(timestamp)) {
throw new BusinessException(ErrorCode.TIME_INVALID);
}
if (StrUtil.isEmpty(sign)) {
throw new BusinessException(ErrorCode.SIGN_ERROR);
}
// 校验请求是否已经过期
validTime(timestamp);
final InnerApiInput innerApiInput = new InnerApiInput()
.setBody(IoUtil.read(inputMessage.getBody(), CharsetUtil.UTF_8))
.setSign(sign)
.setToken(getToken(headers, parameter))
.setTimestamp(timestamp);
// 校验签名是否一致
signCheckout(innerApiInput);
return new HttpInputMessage() {
@SuppressWarnings("NullableProblems")
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(innerApiInput.getBody().getBytes(StandardCharsets.UTF_8));
}
@SuppressWarnings("NullableProblems")
@Override
public HttpHeaders getHeaders() {
return preHandleHttpHeader(headers);
}
};
}
/**
* 获取当前请求对应的token值
*
* @param headers HTTP 请求头
* @param parameter MethodParameter
* @return token值
*/
protected abstract String getToken(HttpHeaders headers, MethodParameter parameter);
/**
* 获取过期时间
* @return timestamp过期时间
*/
protected abstract long getExpiredTime();
protected HttpHeaders preHandleHttpHeader(HttpHeaders originHttpHeaders) {
return originHttpHeaders;
}
/**
* 参数的字段校验
* @param timestamp 过期时间
*/
private void validTime(String timestamp) {
// 校验时间戳是否过期
final long timestampMillis = Convert.toLong(timestamp);
final long currentTimeMillis = TimeUtil.currentTimeMillis();
if (Math.abs(currentTimeMillis - timestampMillis) > getExpiredTime()) {
throw new BusinessException(ErrorCode.TIME_PASSED);
}
}
/**
* 参数的签名校验
* @param innerApiInput
*/
private void signCheckout(InnerApiInput innerApiInput) {
//鉴权开关判断
if (!authConfig.isEnabled()) {
return;
}
if (ArrayUtil.isNotEmpty(authConfig.getExcludeUrls())) {
String accessUrl = ContextUtil.getHttpRequest().getRequestURI();
for (String excludeUrl : authConfig.getExcludeUrls()) {
if (pathMatcher.match(excludeUrl.trim(), accessUrl)) {
return;
}
}
}
//签名校验
String formatStr = String.format(ApiConstants.INNER_MD5_ENC_STR, innerApiInput.getTimestamp(),
innerApiInput.getToken(), innerApiInput.getBody());
String md5 = DigestUtil.md5Hex(formatStr);
if (!StrUtil.equals(innerApiInput.getSign(), md5)) {
log.info("Api sign error!! input:{} server:{}", innerApiInput.getSign(), md5);
throw new BusinessException(ErrorCode.SIGN_ERROR);
}
}
}
通过继承上面的advice去实现真正的逻辑
@ControllerAdvice(
assignableTypes = {
ActivitiesClueController.class,
InnerApiYmsClueController.class
})
public class InnerApiControllerAdvice extends AbstractApiAdvice {
/**
* 过期时间定为10min
*/
private static final long MAX_EXPIRED_DURATION_MILLIS = 10 * 60 * 1000L;
private static final Map<Class<?>, String> INNER_API_TOKEN = new HashMap<Class<?>, String>(3) {{
put(InnerApiSubjectClueController.class, ApiConstants.TOKEN_PUSH_SUBJECT_CLUE);
put(InnerApiIspProjectClueReportController.class, ApiConstants.TOKEN_ISP_PROJECT_CLUE);
put(InnerApiMonitorSubjectClueController.class, ApiConstants.TOKEN_MONITOR_SUBJECT_CLUE);
put(InnerApiYmsClueController.class, ApiConstants.TOKEN_YMS_CLUE);
put(ActivitiesClueController.class, ApiConstants.TOKEN_PUSH_ACTIVITIES_CLUE);
}};
@Override
protected String getToken(HttpHeaders headers, MethodParameter parameter) {
return INNER_API_TOKEN.get(parameter.getContainingClass());
}
@Override
protected long getExpiredTime() {
return MAX_EXPIRED_DURATION_MILLIS;
}
}
其用法跟HandlerInterceptorAdaptor结合webMvcConfigurator差不多,都是起到拦截器的作用,