编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

你这代码写得真丑,满屏的try-catch,全局异常处理不会吗?下

wxchong 2024-09-08 10:49:11 开源技术 9 ℃ 0 评论

统一返回结果



在验证统一异常处理器之前,顺便说一下统一返回结果。说白了,其实是统一一下返回结果的数据结构。code、message 是所有返回结果中必有的字段,而当需要返回数据时,则需要另一个字段 data 来表示。


所以首先定义一个 BaseResponse 来作为所有返回结果的基类;


然后定义一个通用返回结果类CommonResponse,继承 BaseResponse,而且多了字段data;


为了区分成功和失败返回结果,于是再定义一个 ErrorResponse;


最后还有一种常见的返回结果,即返回的数据带有分页信息,因为这种接口比较常见,所以有必要单独定义一个返回结果类 QueryDataResponse,该类继承自CommonResponse,只是把 data 字段的类型限制为 QueryDdata,QueryDdata中定义了分页信息相应的字段,即totalCount、pageNo、 pageSize、records。


其中比较常用的只有 CommonResponse 和 QueryDataResponse,但是名字又贼鬼死长,何不定义2个名字超简单的类来替代呢?于是 R 和 QR 诞生了,以后返回结果的时候只需这样写:new R<>(data)、new QR<>(queryData)。


所有的返回结果类的定义这里就不贴出来了


验证统一异常处理



因为这一套统一异常处理可以说是通用的,所有可以设计成一个 common包,以后每一个新项目/模块只需引入该包即可。所以为了验证,需要新建一个项目,并引入该 common包。项目结构如下:


项目结构

以后只需这样引入即可


引入common包


主要代码


下面是用于验证的主要源码:





















































































@Servicepublic class LicenceService extends ServiceImpl<LicenceMapper, Licence> {


@Autowired private OrganizationClient organizationClient;


/** * 查询{@link Licence} 详情 * @param licenceId * @return */ public LicenceDTO queryDetail(Long licenceId) { Licence licence = this.getById(licenceId); checkNotNull(licence);


OrganizationDTO org = ClientUtil.execute(() -> organizationClient.getOrganization(licence.getOrganizationId())); return toLicenceDTO(licence, org); }


/** * 分页获取 * @param licenceParam 分页查询参数 * @return */ public QueryData<SimpleLicenceDTO> getLicences(LicenceParam licenceParam) { String licenceType = licenceParam.getLicenceType(); LicenceTypeEnum licenceTypeEnum = LicenceTypeEnum.parseOfNullable(licenceType); // 断言, 非空 ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum);


LambdaQueryWrapper<Licence> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Licence::getLicenceType, licenceType); IPage<Licence> page = this.page(new QueryPage<>(licenceParam), wrapper); return new QueryData<>(page, this::toSimpleLicenceDTO); }


/** * 新增{@link Licence} * @param request 请求体 * @return */ @Transactional(rollbackFor = Throwable.class) public LicenceAddRespData addLicence(LicenceAddRequest request) { Licence licence = new Licence(); licence.setOrganizationId(request.getOrganizationId()); licence.setLicenceType(request.getLicenceType()); licence.setProductName(request.getProductName()); licence.setLicenceMax(request.getLicenceMax()); licence.setLicenceAllocated(request.getLicenceAllocated()); licence.setComment(request.getComment()); this.save(licence);


return new LicenceAddRespData(licence.getLicenceId()); }


/** * entity -> simple dto * @param licence {@link Licence} entity * @return {@link SimpleLicenceDTO} */ private SimpleLicenceDTO toSimpleLicenceDTO(Licence licence) { // 省略 }


/** * entity -> dto * @param licence {@link Licence} entity * @param org {@link OrganizationDTO} * @return {@link LicenceDTO} */ private LicenceDTO toLicenceDTO(Licence licence, OrganizationDTO org) { // 省略 }


/** * 校验{@link Licence}存在 * @param licence */ private void checkNotNull(Licence licence) { ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence); }


}


ps: 这里使用的DAO框架是mybatis-plus。
启动时,自动插入的数据

-- licenceINSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)VALUES (1, 1, 'user','CustomerPro', 100,5);INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)VALUES (2, 1, 'user','suitability-plus', 200,189);INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)VALUES (3, 2, 'user','HR-PowerSuite', 100,4);INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)VALUES (4, 2, 'core-prod','WildCat Application Gateway', 16,16);


-- organizationsINSERT INTO organization (id, name, contact_name, contact_email, contact_phone)VALUES (1, 'customer-crm-co', 'Mark Balster', 'mark.balster@custcrmco.com', '823-555-1212');INSERT INTO organization (id, name, contact_name, contact_email, contact_phone)VALUES (2, 'HR-PowerSuite', 'Doug Drewry','doug.drewry@hr.com', '920-555-1212');


oug.drewry@hr.com', '920-555-1212');


开始验证



捕获自定义异常


  1. 获取不存在的 licence 详情:http://localhost:10000/licence/5。成功响应的请求:licenceId=1
    校验非空
    捕获 L

2.根据不存在的 licence type 获取 licence 列表:http://localhost:10000/licence/list?licenceType=ddd。可选的 licence type 为:user、core-prod 。


eType=ddd。可选的 licence type 为:user、core-prod 。


校验非空


捕获 Bad licence type 异常


Bad licence type


捕获进入 Controller 前的异常


  1. 访问不存在的接口:http://localhost:10000/licence/list/ddd
    捕获404异常
  2. http 方法不支持:http://localhost:10000/licence

    PostMapping
    捕获 Request method n

3 校验异常1:http://localhost:10000/licence/list?licenceType

常1:http://localhost:10000/licence/list?licenceType=


ype=


getLicences


LicenceParam


捕获参数绑定校验异常


licence type cannot be empty


4.校验异常2:post 请求,这里使用postman模拟。


addLicence


LicenceAddRequest


请求url即结果


捕获参数绑定校验异常

注:因为参数绑定校验异常的异常信息的获取方式与其它异常不一样,所以才把这2种情况的异常从 进入 Cont

假设我们现在随便对 Licence 新增一个字段 test,但不修改数据库表结构,然后访问:http://localhost:10000/lice

字段 test,但不修改数据库表结构,然后访问:http://localhost:10000/licence/1。


icence/1。


增加test字段


捕获数据库异常


Error querying database


小结



可以看到,测试的异常都能够被捕获,然后以 code、message 的形式返回。每一个项目/模块,在定义业务异常的时候,只需定义一个枚举类,然后实现接口BusinessExceptionAss

在生产环境,若捕获到 未知异常 或者 ServletException,因为都是一长串的异常信息,若直接展示给用户看,显得不够专业,于是,我们可以这样做:当检测到当前环境是生产环境,那么直接返回 "网络异常"。


串的异常信息,若直接展示给


可以通过

样做:当检测到当前环境是生产环境,那么直接返回 "网络异常"。


那么直接返回 "网络异常"


可以通过以下方式修改当前环境:


可以通过以下方式修改当前环境:


前环境:


修改当前环境为生产环境


# 总结



使用 断言 和 枚举类 相结合的方式,再配合统一异常处理,基本大部分的异常都能够被捕获。为什么说大部分异常,因为当引入 spring cloud security 后,还会有认证/授权异常,网关的服务降级异常、跨模块调用异常、远程调用第三方服务异常等,这些异常的捕获方式与本文介绍的不太一样,不过限于篇幅,这里不做详细说明,以后会有单独的文章介绍。


另外,当需要考虑国际化的时候,捕获异常后的异常信息一般不能直接返回,需要转换成对应的语言,不过本文已考虑到了这个,获取消息的时候已经做了国际化映射,逻辑如下:


获取国际化消息


最后总结,全局异常属于老生长谈的话题,希望这次通过手机的项目对大家有点指导性的学习。大家根据实际情况自行修改。


也可以采用以下的jsonResult对象的方式进行处理,也贴出来代码.



















@Slf4j@RestControllerAdvicepublic class GlobalExceptionHandler {


/** * 没有登录 * @param request * @param response * @param e * @return */ @ExceptionHandler(NoLoginException.class) public Object noLoginExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e){ log.error("[GlobalExceptionHandler][noLoginExceptionHandler] exception",e); JsonResult jsonResult = new JsonResult(); jsonResult.setCode(JsonResultCode.NO_LOGIN); jsonResult.setMessage("用户登录失效或者登录超时,请先登录"); return jsonResult; }


/** * 业务异常 * @param request * @param response * @param e * @return */ @ExceptionHandler(ServiceException.class) public Object businessExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e){ log.error("[GlobalExceptionHandler][businessExceptionHandler] exception",e); JsonResult jsonResult = new JsonResult(); jsonResult.setCode(JsonResultCode.FAILURE); jsonResult.setMessage("业务异常,请联系管理员"); return jsonResult; }


/** * 全局异常处理 * @param request * @param response * @param e * @return */ @ExceptionHandler(Exception.class) public Object exceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e){ log.error("[GlobalExceptionHandler][exceptionHandler] exception",e); JsonResult jsonResult = new JsonResult(); jsonResult.setCode(JsonResultCode.FAILURE); jsonResult.setMessage("系统错误,请联系管理员"); return jsonResult; }}

ultCode.FAILURE); jsonResult.setMessage("系统错误,请联系管理员"); return jsonResult; }}


Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表