需求:
Controller
层需要统一返回格式。
实现方案:
- 静态方法(不推荐):非业务代码显示调用没必要。
- AOP(推荐)
异常统一处理,减少显示
try catch
。
一、ResponseBodyAdvice
ResponseBodyAdvice
是通过AOP(Aspect-Oriented Programming,面向切面编程)实现的,接口用于拦截和处理控制器返回的响应数据。具体来说,它允许你在返回给客户端之前,对返回的@ResponseBody
或者@RestController
的响应体进行修改。
统一结果返回类:
@Data
class ResponseWrapper<T> {
private int code;
private String message;
private T data;
public ResponseWrapper(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> ResponseWrapper<T> fail(HttpStatusEnum status, Object... args) {
return new ResponseWrapper<>(status.getCode(), String.format(status.getMsg(), args), null);
}
public static <T> ResponseWrapper<T> fail(HttpStatusEnum status) {
return new ResponseWrapper<>(status.getCode(), status.getMsg(), null);
}
}
定义两个注解,一个用于定义开启统一结果封装,一个定义忽略统一结果封装。
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWrapResponse {
}
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipResponseWrap {
}
切面类:
@ControllerAdvice
@RequiredArgsConstructor
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 判断是否需要进行封装
boolean isAutoWrap = AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), AutoWrapResponse.class) ||
returnType.hasMethodAnnotation(AutoWrapResponse.class);
boolean isSkipWrap = AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), SkipResponseWrap.class) ||
returnType.hasMethodAnnotation(SkipResponseWrap.class);
return isAutoWrap && !isSkipWrap;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 返回封装后的统一格式数据
if (body instanceof ResponseWrapper || body instanceof ResponseEntity) {
return body; // 已经封装的直接返回
}
// 要注意String 类型的情况
if(body instanceof String) {
try {
return this.objectMapper.writeValueAsString(new ResponseWrapper<>(200, "成功", body));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
return new ResponseWrapper<>(200, "成功", body); // 统一封装格式
}
}
注意:String
类型的 selectedConverterType
参数值是 org.springframework.http.converter.StringHttpMessageConverter
,而其他数据类型的值是 org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
。所以如果返回结果是一个 String
类型的数据需要特殊处理。
二、统一异常处理
可以自定义业务逻辑异常,并且增加相关的异常处理。 Exception 我这里定义成返回
500
,前端会跳转到自定义的500异常页面。
@RestControllerAdvice(basePackages="com.github.nan.web")
public class CustomExceptionAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseWrapper handleMethodArgumentNotValidException(MethodArgumentNotValidException ex){
BindingResult bindingResult =ex.getBindingResult();
StringBuilder stringBuilder = new StringBuilder("校验失败:");
for ( FieldError fieldError : bindingResult.getFieldErrors()) {
stringBuilder.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(";");
}
return ResponseWrapper.fail(HttpStatusEnum.ILLEGAL_ARGUMENTS_MIXED, stringBuilder.toString());
}
@ExceptionHandler(Exception.class)
public ResponseEntity handleException(Exception ex) {
// 记录异常日志
ex.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}