2. NOT统一响应
- 不开启统一响应原因
开发小哥是开心了,可是其他系统就不开心了。举个例子:我们项目中集成了一个健康检测的功能,也就是这货
@RestController public class HealthController { @GetMapping("/health") public String health() { return "success"; } }
公司部署了一套校验所有系统存活状态的工具,这工具就定时发送get请求给我们系统
- “兄弟,你死了吗?”
- “我没死,滚”
- “兄弟,你死了吗?”
- “我没死,滚”
是的,web项目的本质就是复读机。一旦发送的请求没响应,就会给负责人发信息(企业微信或者短信之类的),你的系统死啦!赶紧回来排查bug吧!让大家感受一下。每次看到我都射射发抖,早上6点!我tm!!!!!
好吧,没办法,人家是老大,人家要的返回不是
{ "code": 1000, "msg": "请求成功", "data": "success" }
人家要的返回只要一个success,人家定的标准不可能因为你一个系统改。俗话说的好,如果你改变不了环境,那你就只能我****
- 新增不进行封装注解
因为百分之99的请求还是需要包装的,只有个别不需要,写在包装的过滤器吧?又不是很好维护,那就加个注解好了。所有不需要包装的就加上这个注解。
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface NotControllerResponseAdvice { }
然后在我们的增强过滤方法上过滤包含这个注解的方法
@RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) 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)); } ...
最后就在不需要包装的方法上加上注解
@RestController public class HealthController { @GetMapping("/health") @NotControllerResponseAdvice public String health() { return "success"; } }
这时候就不会自动封装了,而其他没加注解的则依旧自动包装
五、统一异常
每个系统都会有自己的业务异常,比如库存不能小于0子类的,这种异常并非程序异常,而是业务操作引发的异常,我们也需要进行规范的编排业务异常状态码,并且写一个专门处理的异常类,最后通过刚刚学习过的异常拦截统一进行处理,以及打日志
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; } }
2.异常类,这里需要强调一下,code代表AppCode的异常状态码,也就是2000;msg代表业务异常,这只是一个大类,一般前端会放到弹窗title上;最后super(message);
这才是抛出的详细信息,在前端显示在弹窗体中,在ResultVo
则保存在data中
@Getter public class APIException extends RuntimeException { private int code; private String msg; // 手动设置异常 public APIException(StatusCode statusCode, String message) { // message用于用户设置抛出错误详情,例如:当前价格-5,小于0 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.最后进行统一异常的拦截,这样无论在service
层还是controller
层,开发人员只管抛出API异常,不需要关系怎么返回给前端,更不需要关心日志的打印
@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.最后使用,我们的代码只需要这么写
if (null == orderMaster) { throw new APIException(AppCode.ORDER_NOT_EXIST, "订单号不存在:" + orderId); } { "code": 2003, "msg": "订单不存在", "data": "订单号不存在:1998" }
就会自动抛出AppCode.ORDER_NOT_EXIST
状态码的响应,并且带上异常详细信息订单号不存在:xxxx。后端小哥开发有效率,前端妹妹获取到2003状态码,调用对应警告弹窗,title写上订单不存在,body详细信息记载"订单号不存在:1998"。同时日志还自动打上去了!666!老哥们三连点个赞!