本文内容
步骤
一、定义统一返回信息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GlobalApiResponse<T> {
private static final String SUCCESS_CODE = "success";
private String code;
private String msg;
private T data;
public static <T> GlobalApiResponse<T> success(T result) {
return new GlobalApiResponse<>(SUCCESS_CODE, null, result);
}
}
我们所有的响应信息都遵循这个格式
我们的 code 应该是 success、xxx error 这种简短的错误信息,而不应该是一个数字,code 应该能够简单的体现出错误的种类。
msg 则是错误的进一步的具体信息,比如:xxx 地方发生了 xx 错误,原因是 xx
二、定义统一异常类
public enum BusinessExceptionEnum {
DEMO_ERROR("用来测试的demo异常", "demo error", "这是一个demo错误,原因是:%s"),
PARAMETER_INVALID_ERROR("参数校验不通过的时候抛出此异常","parameter invalid","%s"),
MAX_UPLOAD_SIZE_ERROR("上传的文件超过大小限制时抛出此异常","max upload size limited",
"请求或者文件大小超过限制值:%s"),
INTERNAL_SYSTEM_ERROR("内部系统异常,正常来讲不应该抛出这个异常,一旦发现这个异常应该在统一异常处理中添加新种类的异常",
"internal system error","系统内部发生异常");
// info信息说明这个异常发生的时机
private String info;
private String code;
private String msg;
BusinessExceptionEnum(String info, String code, String msg) {
this.info = info;
this.code = code;
this.msg = msg;
}
public BusinessException toException(Object... args) {
String detailMsg = String.format(msg, args);
return new BusinessException(code, detailMsg);
}
@Getter
@Setter
@AllArgsConstructor
public static class BusinessException extends RuntimeException {
private String code;
private String msg;
}
}
我们所有的异常都要包装成 BusinessException
通过 DEMO_ERROR.toException("some message") 的方式来生成新的 BusinessException
三、定义统一异常处理
@Slf4j
@ControllerAdvice
public class GlobalExceptionAdvice {
/**
* 处理通用的自定义业务异常
*/
@ExceptionHandler(BusinessException.class)
public BusinessException handleBusinessException(BusinessException e, HttpServletRequest request) {
log.info("处理 BusinessException,URL:【{}】, 原因:【{}】", request.getRequestURI(), e.getMsg());
return e;
}
/**
* 处理参数校验异常
* 使用 @Validated 注解在 controller 类上时校验非 Model 类型的参数失败时的异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public BusinessException handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
log.info("处理 MethodArgumentNotValidException,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
String errorMsg = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> String.format("请求参数【%s】%s", error.getField(), error.getDefaultMessage()))
.collect(Collectors.joining(","));
return PARAMETER_INVALID_ERROR.toException(errorMsg);
}
/**
* 处理参数绑定时的异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public BusinessException handleBindException(BindException e, HttpServletRequest request) {
log.info("处理 BindException,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
String errorMsg = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> String.format("请求参数【%s】%s", error.getField(), error.getDefaultMessage()))
.collect(Collectors.joining(","));
return PARAMETER_INVALID_ERROR.toException(errorMsg);
}
/**
* 处理参数绑定时的异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public BusinessException handleConstraintViolationException(ConstraintViolationException e,
HttpServletRequest request) {
log.info("处理 ConstraintViolationException,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
String errorMsg = e.getConstraintViolations()
.stream()
.map(constraintViolation -> {
String propertyPath = constraintViolation.getPropertyPath().toString();
if (propertyPath.contains(".")) {
String[] propertyPathArr = propertyPath.split("\\.");
propertyPath = propertyPathArr[propertyPathArr.length - 1];
}
return String.format("请求参数【%s】%s", propertyPath, constraintViolation.getMessage());
})
.collect(Collectors.joining(","));
return PARAMETER_INVALID_ERROR.toException(errorMsg);
}
/**
* 处理参数转换时的异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public BusinessException handleHttpMessageNotReadableException(HttpMessageNotReadableException e,
HttpServletRequest request) {
log.info("处理 HttpMessageNotReadableException,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
return PARAMETER_INVALID_ERROR.toException("请求参数异常,请检查参数类型");
}
/**
* 处理参数转换时的异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public BusinessException handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
HttpServletRequest request) {
log.info("处理 MethodArgumentTypeMismatchException,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
return PARAMETER_INVALID_ERROR.toException("请求参数异常,请检查参数类型");
}
/**
* 处理上传文件超出限制的异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MaxUploadSizeExceededException.class)
public BusinessException handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e,
HttpServletRequest request) {
log.info("处理 MaxUploadSizeExceededException,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
return MAX_UPLOAD_SIZE_ERROR.toException(e.getMaxUploadSize() + " 字节");
}
/**
* 处理其它异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(Throwable.class)
public BusinessException handleThrowable(Throwable e,
HttpServletRequest request) {
log.info("处理 Throwable,URL:【{}】, 原因:【{}】",
request.getRequestURI(), e);
return INTERNAL_SYSTEM_ERROR.toException();
}
}
四、定义统一响应处理
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
private static final String API_FALG = "/";
// 定义在哪些方法上支持统一响应
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
if (o instanceof BusinessException) {
serverHttpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
BusinessException businessException = (BusinessException) o;
return new GlobalApiResponse<>(businessException.getCode(), businessException.getMsg(), null);
}
if(o instanceof String){
String str = (String) o;
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(GlobalApiResponse.success(str));
}
if (serverHttpRequest.getURI().toString().contains(API_FALG)) {
return GlobalApiResponse.success(o);
}
return o;
}
}
我们的统一响应会作用在包含 API_FALG 的 URL 返回值上