一、统一状态码
1、首先先定义一个状态码
的接口,所有状态码
都需要实现它,有了标准才好做事
public interface StatusCode {
public int getCode();
public String getMsg();
}
2、约定好状态码枚举类,当然不能有setter
方法了,因此我们不能在用@Data
注解了,我们要用@Getter
@Getter
public enum ResultCode implements StatusCode{
SUCCESS(1000, "请求成功"),
FAILED(1001, "请求失败"),
VALIDATE_ERROR(1002, "参数校验失败"),
RESPONSE_PACK_ERROR(1003, "response返回包装失败");
private int code;
private String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
3、写好枚举类,就开始写ResultVo
包装类了,例如成功的话就默认传入object
就可以了,我们自动包装成success
@Data
public class ResultVo {
// 状态码
private int code;
// 状态信息
private String msg;
// 返回对象
private Object data;
// 手动设置返回vo
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// 默认返回成功状态码,数据对象
public ResultVo(Object data) {
this.code = ResultCode.SUCCESS.getCode();
this.msg = ResultCode.SUCCESS.getMsg();
this.data = data;
}
// 返回指定状态码,数据对象
public ResultVo(StatusCode statusCode, Object data) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
this.data = data;
}
// 只返回状态码
public ResultVo(StatusCode statusCode) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
this.data = null;
}
}
4、使用
@PostMapping("/findByVo")
public ResultVo findByVo(@Validated ProductInfoVo vo) {
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(vo, productInfo);
return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}
二、统一校验
@Validated参数校验
@Data
public class ProductInfoVo {
@NotNull(message = "商品名称不允许为空")
private String productName;
@Min(value = 0, message = "商品价格不允许为负数")
private BigDecimal productPrice;
private Integer productStatus;
}
三、统一响应
@RestControllerAdvice(basePackages = {"com.ff.test"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装
return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
|| methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVo里后转换为json串进行返回
return objectMapper.writeValueAsString(new ResultVo(data));
} catch (JsonProcessingException e) {
throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
}
}
// 否则直接包装成ResultVo返回
return new ResultVo(data);
}
}
@RestControllerAdvice(basePackages = {"com.ff.test"})
自动扫描了所有指定包下的controller
,在Response
时进行统一处理- 重写
supports
方法,也就是说,当返回类型已经是ResultVo
了,那就不需要封装了,当不等与ResultVo
时才进行调用beforeBodyWrite
方法,跟过滤器的效果是一样的 - 最后重写我们的封装方法
beforeBodyWrite
,注意除了String
的返回值有点特殊,无法直接封装成json,我们需要进行特殊处理,其他的直接new ResultVo(data);
就ok了
四、排除统一响应
新增不进行封装注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}
使用:不需要包装的方法上加上注解
五、统一异常
1、异常状态码枚举,既然是状态码,那就肯定要实现我们的标准接口StatusCode
@Getter
public enum AppCode implements StatusCode {
APP_ERROR(2000, "业务异常"),
PRICE_ERROR(2001, "价格异常");
private int code;
private String msg;
AppCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
@Getter
public class APIException extends RuntimeException {
private int code;
private String msg;
// 手动设置异常
public APIException(StatusCode statusCode, String message) {
super(message);
// 状态码
this.code = statusCode.getCode();
// 状态码配套的msg
this.msg = statusCode.getMsg();
}
// 默认异常使用APP_ERROR状态码
public APIException(String message) {
super(message);
this.code = AppCode.APP_ERROR.getCode();
this.msg = AppCode.APP_ERROR.getMsg();
}
}
3、最后进行统一异常的拦截
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class})
public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
}
@ExceptionHandler(APIException.class)
public ResultVo APIExceptionHandler(APIException e) {
// log.error(e.getMessage(), e); 由于还没集成日志框架,暂且放着,写上TODO
return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
}
}
4、使用
throw new APIException(AppCode.ORDER_NOT_EXIST, "订单号不存在:" + orderId);