断言+异常处理类,代码更简洁了

简介: 断言+异常处理类,代码更简洁了
  • 背景
  • 业务异常处理示例
  • 附上代码

背景

软件开发过程中,不可避免的是需要处理各种异常,所以代码中就会出现大量的 try {...} catch {...} finally {...} 代码块,不仅有大量的冗余代码,而且还影响代码的可读性。

另一个就是面对业务异常的情况,我们经常需要将业务异常结果组装成统一的信息返回给前端进行提示。

假如我们在每个接口中都去包装异常信息进行返回就会让代码变得很冗余且混乱。在我司的实际项目开发过程中,我们会巧用断言去简化代码。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

业务异常处理示例

假设我们定义的标准接口响应实体为 ApiResult:

@Data
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
 public class ApiResult<T> implements Serializable {
     private static final long serialVersionUID = 411731814484355577L;
     private int responseCode;
     private String responseMsg;
     private boolean isSuccess;
     private T data;
     public String toString() {
         return "ApiResult(responseCode=" + this.getResponseCode() + ", responseMsg=" + this.getResponseMsg() + ", isSuccess=" + this.isSuccess() + ", data=" + this.getData() + ")";
     }
 }

那么我们接口处理业务逻辑时代码就会变成这样,看起来非常多代码:

public ApiResult cancelService(@PathVariable Long serviceOrderId){
     ServiceOrder serviceOrder = serviceOrderMapper.selectByPrimaryKey(serviceOrderId);
     ApiResult result = new ApiResult<>();
     if (ObjectUtil.isNull(serviceOrder)) {
         result.setSuccess(false);
         result.setResponseCode(ErrorCodeEnum.FAIL.getCode());
         result.setResponseMsg("查无此服务单");
         return result;
     }
     if(serviceOrder.getOrderStatus().equals(cancelOrderStatus)){
         result.setSuccess(false);
         result.setResponseCode(ErrorCodeEnum.FAIL.getCode());
         result.setResponseMsg("已取消的服务单不允许再次取消");
         return result;
     }
     if(serviceOrder.getSortOrderId() != null){
         result.setSuccess(false);
         result.setResponseCode(ErrorCodeEnum.FAIL.getCode());
         result.setResponseMsg("已配置物料的服务单不允许取消");
         return result;
     }
     // ...other check
     // ...do something
     return result;
 }

然后在上面这个代码基础上,我们可以观察到,里面其实有非常多的重复代码,完全可以把它们装到 ApiResult 里面。

这也是我看到很多开源框架的处理方式(PS:所以我第一个自己写的框架也是这么处理的)

在原 ApiResult 实体中增加一些公用的处理方法:

public static ApiResult<String> success() {
     return success("success");
 }
 public static <T> ApiResult<T> success(T data) {
     return (new ApiResult()).setResponseCode(0).setResponseMsg("操作成功").setSuccess(true).setData(data);
 }
 public static ApiResult<String> fail() {
     return fail(-1);
 }
 public static ApiResult<String> fail(int code) {
     return fail(code, "fail");
 }
 public static <T> ApiResult<T> fail(T data) {
     return fail(-1, data);
 }
 public static <T> ApiResult<T> fail(int code, T data) {
     return (new ApiResult()).setResponseCode(code).setResponseMsg("操作失败").setSuccess(false).setData(data);
 }
 public static <T> ApiResult<T> success(int code, String message, T data) {
     return (new ApiResult()).setResponseCode(code).setResponseMsg(message).setSuccess(true).setData(data);
 }
 public static <T> ApiResult<T> fail(int code, String message, T data) {
     return (new ApiResult()).setResponseCode(code).setResponseMsg(message).setSuccess(false).setData(data);
 }

然后业务逻辑处理就变成这样了,看起来还不错是不是:

/**
  * 取消服务单(不用断言)
  */
 public ApiResult cancelService(Long serviceOrderId){
     ServiceOrder serviceOrder = serviceOrderMapper.selectByPrimaryKey(serviceOrderId);
     ApiResult result = new ApiResult<>();
     if (ObjectUtil.isNull(serviceOrder)) {
         result = ApiResult.fail(ErrorCodeEnum.FAIL.getCode(), "查无此服务单");
         return result;
     }
     if(serviceOrder.getOrderStatus().equals(cancelOrderStatus)){
         result = ApiResult.fail(ErrorCodeEnum.FAIL.getCode(), "已取消的服务单不允许再次取消");
         return result;
     }
     if(serviceOrder.getSortOrderId() != null){
         result = ApiResult.fail(ErrorCodeEnum.FAIL.getCode(), "已配置物料的服务单不允许取消");
         return result;
     }
     // ...other check
     // ...do something
     return result;
 }

但是我们可以用异常处理类+断言处理得更加简化。

增加异常处理类:

@Slf4j
 @ControllerAdvice
 public class GlobalExceptionHandler {
     @ExceptionHandler(value = BusinessException.class)
     @ResponseBody
     public ResponseBean businessExceptionHandler(BusinessException e) {
         log.info("business error : {}",e.getMessage(),e);
         if (e.getCode() == -1) {
             return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
         }
         return ResponseBean.error(e.getCode(), e.getMessage());
     }
 }

增加异常类 BusinessException:

/**
  * 业务异常,异常信息会返回到前端展示给用户
  *
  * @date 2020/12/15 14:18
  */
 public class BusinessException extends RuntimeException {
     private static final long serialVersionUID = -5770538329754222306L;
     private int code = 1;
     private Level level;
     public BusinessException(int code, String message, Throwable cause) {
         super(message, cause);
         this.code = code;
     }
     public BusinessException(String message) {
         super(message);
     }
     public BusinessException(Level level, String message) {
         super(message);
         this.level = level;
     }
     public BusinessException(Throwable cause) {
         super(cause);
     }
     public BusinessException(int code, String message) {
         super(message);
         this.code = code;
     }
     public int getCode() {
         return this.code;
     }
     public final Level getLevel() {
         return this.level;
     }
 }

增加断言工具类 AssertUtil:

public class AssertUtil extends cn.com.bluemoon.common.web.exception.AssertUtil  {
     public AssertUtil() {
     }
     /**
      * 服务调用异常
      * @param expression
      * @param message
      */
     public static void isTrueServiceInvoke(boolean expression, String message) {
         if (!expression) {
             throw new ServiceInvokeException(message);
         }
     }
     /**
      * 抛出异常(默认错误1000)
      * @param message
      */
     public static void businessInvalid(String message) {
         throw new BusinessException(ApiCode.SERVICE_ERROR.getValue(), message);
     }
     /**
      * 表达式为真即抛出异常(默认错误1000)
      *
      * @param expression
      * @param message
      */
     public static void businessInvalid(boolean expression, String message) {
         if (expression) {
             throw new BusinessException(ApiCode.SERVICE_ERROR.getValue(), message);
         }
     }
     /**
      * 表达式为真即抛出异常
      *
      * @param expression
      * @param message
      */
     public static void businessInvalid(boolean expression, int code, String message) {
         if (expression) {
             throw new BusinessException(code, message);
         }
     }
 }

最后优化的结果:

/**
  * 取消服务单
  */
 public ApiResult cancelService(@PathVariable Long serviceOrderId){
     ServiceOrder serviceOrder = serviceOrderMapper.selectByPrimaryKey(serviceOrderId);
     AssertUtil.businessInvalid(ObjectUtil.isNull(serviceOrder),"查无此服务单");
     AssertUtil.businessInvalid(serviceOrder.getOrderStatus().equals(cancelOrderStatus),"查无此服务单");
     AssertUtil.businessInvalid(serviceOrder.getSortOrderId() != null,"查无此服务单");
     // ...other check
     // ...do something
     return ApiResult.success();
 }

最后,我们可以看到我们的接口由 19 行的业务检查代码简化到了 3 行。这只是单接口的情况下,在业务多且复杂的情况下能给我们节省更多的开发时间,把精力集中在核心业务上。

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

附上代码

统一异常处理类:

/**
  * 统一异常处理
  */
 @Slf4j
 @ControllerAdvice
 public class GlobalExceptionHandler {
     @ExceptionHandler(value = AssertException.class)
     @ResponseBody
     public ResponseBean bootExceptionHandler(AssertException e) {
         ApiCode apiCode = ApiCode.getObjectByValue(e.getCode());
         log.error("business error : {}", e.getMessage(), e);
         if (e.getCode() == -1) {
             return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
         }
         return ResponseBean.error(apiCode.getValue(), e.getMessage());
     }
     @ExceptionHandler(value = com.alibaba.fastjson.JSONException.class)
     public ResponseBean alibabaJsonExceptionHandler(com.alibaba.fastjson.JSONException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), ApiCode.PARAM_FORMAT_INCORR.getMessage() + e.getMessage(), null);
         log.error("1102", e);
         return response;
     }
     @ExceptionHandler(value = JSONException.class)
     @ResponseBody
     public ResponseBean jsonExceptionHandler(JSONException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), ApiCode.PARAM_FORMAT_INCORR.getMessage() + e.getMessage(), null);
         log.error(ApiCode.PARAM_FORMAT_INCORR.getValue() + "", e);
         return response;
     }
     @ExceptionHandler(value = JsonParseException.class)
     @ResponseBody
     public ResponseBean jsonParseExceptionHandler(JsonParseException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), String.format(ApiCode.PARAM_FORMAT_INCORR.getMessage() + ":%s", e.getMessage()), null);
         log.error(ApiCode.PARAM_FORMAT_INCORR.getValue() + "", e);
         return response;
     }
     @ExceptionHandler(value = Exception.class)
     @ResponseBody
     public ResponseBean exceptionHandler(Exception e) {
         ResponseBean response = new ResponseBean(false, ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage(), null);
         log.error(ApiCode.SERVICE_ERROR.getValue() + "", e);
         return response;
     }
     @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
     @ResponseBody
     public ResponseBean exceptionHandle(MethodArgumentTypeMismatchException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), String.format(ApiCode.PARAM_FORMAT_INCORR.getMessage() + ":%s", e.getMessage()), null);
         log.error(ApiCode.PARAM_FORMAT_INCORR.getValue() + "", e);
         return response;
     }
     @ExceptionHandler(value = WebException.class)
     @ResponseBody
     public ResponseBean exceptionHandler(WebException e) {
         ResponseBean response = new ResponseBean(e.getIsSuccess(), e.getResponseCode(), e.getResponseMsg(), null);
         log.error(e.getResponseCode() + "", e);
         return response;
     }
     @ExceptionHandler(value = IllegalArgumentException.class)
     @ResponseBody
     public ResponseBean exceptionHandler(IllegalArgumentException e) {
         log.error("illegal request : {}", e.getMessage(), e);
         return ResponseBean.error(ApiCode.PARAM_INVALID.getValue(), ApiCode.PARAM_INVALID.getMessage());
     }
     @ExceptionHandler(value = ServiceInvokeException.class)
     @ResponseBody
     public ResponseBean exceptionHandler(ServiceInvokeException e) {
         log.error("serviceInvoke error request : {}", e.getMessage(), e);
         return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
     }
     @ExceptionHandler(value = BusinessException.class)
     @ResponseBody
     public ResponseBean businessExceptionHandler(BusinessException e) {
         log.info("business error : {}",e.getMessage(),e);
         if (e.getCode() == -1) {
             return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
         }
         return ResponseBean.error(e.getCode(), e.getMessage());
     }
     @ResponseBody
     @ExceptionHandler(MethodArgumentNotValidException.class)
     public ResponseBean  exceptionHandler(MethodArgumentNotValidException e) {
         log.info("req params error", e);
         String message = e.getBindingResult().getFieldError().getDefaultMessage();
         if (StringUtils.isNotBlank(message) && !"不能为空".equals(message)) {
             return ResponseBean.error(ApiCode.PARAM_INVALID.getValue(), message);
         }
         return ResponseBean.error(ApiCode.PARAM_INVALID.getValue(), ApiCode.PARAM_INVALID.getMessage());
     }
     @ExceptionHandler(value = TokenErrorException.class)
     @ResponseBody
     public ResponseBean tokenErrorExceptionHandler(TokenErrorException e) {
         log.info("登录失效 : {}",e.getMessage(),e);
         return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), "登录已失效,请重新登录!");
     }
     @ExceptionHandler(value = ServiceException.class)
     @ResponseBody
     public ResponseBean businessExceptionHandler(ServiceException e) {
         log.info("service error : {}",e.getMessage(),e);
         return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), e.getMessage());
     }
 }

异常情况枚举,仅作参考:

public enum ErrorCodeEnum implements EnumBase{
     FAIL(-1, "网络异常,请稍后再试"),
     SUCCESS(0, "请求成功"),
     MAX_UPLOAD_SIZE_ERROR(1000, "上传文件不能超过20M"),
     SERVICE_BUSY_ERROR(1000, "服务器正在繁忙,请稍后再试哦~"),
     REQUEST_PARAMS_FAIL(1001, "参数错误"),
     USER_NOT_LOGIN(1002, "用户未登录,请重新登录"),
     USER_HAS_EXIST_LOGIN(1007, "用户已经存在,请检查!"),
     USER_CODE_NOT_EXIST(1008, "用户编码不存在,请检查!"),
     REQUEST_PARAMS_FORMAT_ERROR(1102, "请求参数格式异常"),
     PASSWORD_SAFETY_ERROE(2204, "密码不符合安全规则,请通过忘记密码重新设置8-18位数字+字母组合密码"),
     TOKEN_EXPIRED(2301, "token过期"),
     TOKEN_ERROR(2302, "token验证失败"),
     INTERFACE_ERROR(10000, "接口服务器异常");
     private final int code;
     private final String msg;
     ErrorCodeEnum(int code, String msg) {
         this.code = code;
         this.msg = msg;
     }
     @Override
     public int getCode() {
         return this.code;
     }
     @Override
     public String getMsg() {
         return this.msg;
     }
 }


相关文章
|
存储 SQL 分布式计算
Hologres+MaxCompute数据仓库服务化详解
介绍如何基于Hologres和MaxCompute产品组合,支撑高并发、快响应的数据服务化场景,替换HBase开发模式,实现数据资产服务化在线化能力。
21847 0
 Hologres+MaxCompute数据仓库服务化详解
|
数据采集 移动开发 文字识别
服务阿里 9 个APP|揭秘新奥创升级的质量演变
新奥创技术体系,是手机淘宝端搭载着星环中台的整个商业化研发体系,孵化出的面对无线电商领域的技术体系。过去一年在手淘完成了下单、详情、购物车三大业务域的改造,接下来还会在订单、手淘导购等领域进行技术升级。目前新奥创已经接入阿里内的9个 App,逐步成为阿里集团无线领域电商系的技术解决方案。 本文主要围绕新奥创技术体系的升级,剖析架构升级对测试保障带来的新的转变,也是新的机遇。
6399 0
服务阿里 9 个APP|揭秘新奥创升级的质量演变
|
数据可视化 API 数据库
低代码是什么?2025低代码技术详解:平台分类、用户群体与发展趋势分析
低代码(Low Code)是一种通过可视化工具和预构建组件,以少量或无代码快速开发应用的技术。2014年Forrester定义其为“用最少手工编码快速开发并部署应用的技术”,随后Gartner推广aPaaS/iPaaS概念推动其发展。
1113 32
|
3月前
|
人工智能 文字识别 自然语言处理
祁木CAD Translator 原理拆解:基于百炼云技术的DWG/DXF工程图纸结构化翻译技术解析
本文解析DWG/DXF图纸结构化翻译技术,突破OCR识别不准、插件兼容差、人工效率低、通用AI误译等瓶颈;首创三层解耦架构,实现文本精准提取、术语智能保护、动态重排回填、多模式译文写入及本地离线私有化部署,支持中俄/中英/中西/中越等多语种批量高保真翻译,赋能设计院、工程外贸与涉密项目高效合规交付。
527 3
|
9月前
|
人工智能 架构师 搜索推荐
AI Agent搭建师的跨界突围:破解职业焦虑,做产业数字化浪潮中的“翻译官”与架构师
在AI冲击下,技术从业者面临职业焦虑。本文提出“AI Agent搭建师”新角色:不做代码搬运工,而当“产业翻译官”,将AI技术与制造业、农业、医疗等传统行业深度融合,用智能体打通数字化“断层”。通过业务—技术双向翻译、人机协作架构设计及可量化ROI验证,构建不可替代的职业护城河,开辟跨界晋升新路径。
440 5
|
缓存 安全 前端开发
Verdaccio私仓搭建的一些注意点和姿势
为什么会搞这个? 是想把我们公司的npm私有仓从nexus迁移出来. 我们目前和后端共用nexus,因为nexus也能管理maven这些! npm私有仓库独立出来,后续好推向整个公司!! 我们这里采用docker自己部署(用的是v4.6.x),方便后续滚动更新. 配置文件及模块缓存目录是从外部映射进去,维护比较弹性 外网访问是通过nginx代理,内网集群是通过安全组策略授权.
2166 0
|
Linux Python
【Azure Function】Python Function部署到Azure后报错No module named '_cffi_backend'
ERROR: Error: No module named '_cffi_backend', Cannot find module. Please check the requirements.txt file for the missing module.
540 2
|
存储 Ubuntu 算法
KNIME学习记录
KNIME学习记录
1366 0
|
存储 Go
速绘丨GO富集气泡图绘制方法,利用R语言ggplot2包快速绘制,完整脚本可重复绘图
速绘丨GO富集气泡图绘制方法,利用R语言ggplot2包快速绘制,完整脚本可重复绘图
|
机器学习/深度学习 Ubuntu Linux
操作系统实验一到实验九合集(哈工大李治军)(三)
操作系统实验一到实验九合集(哈工大李治军)(三)
1164 0
操作系统实验一到实验九合集(哈工大李治军)(三)