当前位置: 首页 > 科技观察

后端思考篇:统一参数校验、异常处理、结果返回

时间:2023-03-14 08:42:19 科技观察

前言大家好,我是捡蜗牛的小男孩。今天的文章比较简单~。在日常工作中,我们在开发一个接口的时候,一般会涉及参数校验、异常处理、打包结果返回等。如果每个后端开发在参数校验和异常处理方面都单独编写,没有统一处理,代码会不优雅,也不易维护。因此,作为一名合格的后端开发工程师,我们需要统一验证参数、统一异常处理、统一结果返回,让代码更规范、更易读、更易维护。1、使用注解,统一参数校验假设罗小天实现了注册用户的功能。在controller层,他会先校验参数,如下:{return"用户名不能为空";}if(StringUtils.isEmpty(userParam.getPhone())){return"电话号码不能为空";}if(userParam.getPhone().length()>11){return"电话号码不能超过11";}if(StringUtils.isEmpty(userParam.getEmail())){return"邮箱地址不能为空";}//省略其他参数验证//todo插入用户信息表return"SUCCESS";}}上面的代码有什么问题?其实没什么问题,就是验证有点眼花。普通的添加用户业务还没写,还有很多参数验证。假设稍后,小天罗又收到了一个请求:编辑用户信息。编辑用户信息前,先验证信息,如下:@RequestMapping("editUser")publicStringeditUser(UserParamuserParam){if(StringUtils.isEmpty(userParam.getUserName())){return"用户名不能为空";}if(StringUtils.isEmpty(userParam.getPhone())){return"电话号码不能为空";}if(userParam.getPhone().length()>11){return"电话号码不能超过11";}if(StringUtils.isEmpty(userParam.getEmail())){return"Emailcannotbeempty";}//省略其他参数校验//todo编辑用户信息表return"SUCCESS";}我们可以使用注解方式进行参数校验,这样代码更简洁,方便统一管理。事实上,springboot有一个验证组件,我们可以开箱即用。导入这个包就可以了:如下:publicclassUserParam{@NotNull(message="用户名不能为空")privateStringuserName;@NotNull(message="手机号码不能为空")@Max(value=11)privateStringphone;@NotNull(message="邮箱不能为空")privateStringemail;然后在UserParam参数对象上加上@Validated注解,将错误信息接收到BindingResult对象中。代码如下:@RequestMapping("addUser")publicStringaddUser(@ValidatedUserParamuserParam,BindingResultresult){ListfieldErrors=result.getFieldErrors();如果(!fieldErrors.isEmpty()){返回fieldErrors.get(0).getDefaultMessage();}//todo插入用户信息表return"SUCCESS";}2。返回接口的统一响应对象如果你在项目代码中看到controller层消息返回的结果,有这样的:@RequestMapping("/hello")publicStringgetStr(){return"hello,the捡蜗牛的小男孩";}//return你好,捡蜗牛的小男孩还有这个:@RequestMapping("queryUser")publicUserVoqueryUser(StringuserId){returnnewUserVo("666","捡蜗牛的小男孩");}//return:{"userId":"666","name":"捡蜗牛的小男孩"}显然,如果接口返回的结果不一致,前端处理起来很不方便,我们的代码也不容易维护。比如小蜗牛喜欢用Result处理结果,大蜗牛喜欢用Response处理结果,可想而知这些代码有多乱。所以作为后端开发,我们项目的响应结果需要一个统一标准的返回格式。通常,标准响应消息对象具有哪些属性?code:响应状态码message:响应结果描述data:返回数据响应状态码一般用枚举表示:publicenumCodeEnum{/**操作成功**/SUCCESS("0000","操作成功"),/**操作失败**/ERROR("9999","操作失败"),;/***自定义状态码**/privateStringcode;/**自定义描述**/privateStringmessage;CodeEnum(Stringcode,Stringmessage){this.code=code;this.message=消息;}publicStringgetCode(){返回代码;}publicStringgetMessage(){返回消息;}}因为返回的数据类型不确定,我们可以使用泛型,如下:/***@author捡蜗牛的小男孩*@param*/publicclassBaseResponse{/***响应状态码(0000表示成功,9999表示失败*/privateStringcode;/***响应结果描述*/privateStringmessage;/***返回数据*/privateT数据;/***成功返回*@参数数据*@param*@return*/publicstaticBaseResponsesuccess(Tdata){BaseResponseresponse=newBaseResponse<>();response.setCode(CodeEnum.SUCCESS.getCode());response.setMessage(CodeEnum.SUCCESS.getMessage());response.setData(数据);返回响应;}/***失败返回*@paramcode*@parammessage*@param*@return*/publicstaticBaseResponsefail(Stringcode,Stringmessage){BaseResponseresponse=新的基础响应<>();响应.setCode(代码);response.setMessage(消息);返回响应;}publicvoidsetCode(Stringcode){this.code=code;}publicvoidsetMessage(Stringmessage){this.message=message;}publicvoidsetData(Tdata){this.data=data;}}有了统一的响应体,我们可以优化controller层的代码:@RequestMapping("/hello")publicBaseResponsegetStr(){returnBaseResponse.success("你好,捡蜗牛的小男孩");}//输出{"code":"0000","message":"操作成功","data":"hello,捡蜗牛的小男孩"}@RequestMapping("queryUser")publicBaseResponsequeryUser(StringuserId){returnBaseResponse.success(newUserVo("666","捡蜗牛的小男孩"));}//output{"code":"0000","message":"操作成功","data":{"userId":"666","name":"捡蜗牛的小男孩"}}3.统一异常在日常开发中,我们一般会自定义和统一异常类,如下:){this.retCode=retCode;this.retMessage=retMessage;}publicStringgetRetCode(){returnretCode;}publicStringgetRetMessage(){returnretMessage;}}在controller层,可能有类似的代码:@RequestMapping("/query")publicBaseResponsequeryUserInfo(UserParamuserParam){try{returnBaseResponse.success(userService.queryUserInfo(userParam));}catch(BizExceptione){//doSomething}catch(Exceptione){//doSomething}returnBaseResponse.fail(CodeEnum.ERROR.getCode(),CodeEnum.ERROR.getMessage());}这段代码没有问题,但是如果try太多了。。.catch,不是很优雅可以使用注解@RestControllerAdvice让代码更优雅。@RestControllerAdvice是应用于Controller层的切面注解。一般与@ExceptionHandler注解一起使用,作为项??目的全局异常处理。让我们看一下演示代码。还是原来的UserController,和一个抛出异常的userService方法,如下:@RestControllerpublicclassUserController{@AutowiredprivateUserServiceuserService;@RequestMapping("/query")publicBaseResponsequeryUserInfo1(UserParamuserParam){returnBaseResponse.success(userService.queryUserInfo(userParam));}}@ServicepublicclassUserServiceImplimplementsUserService{//抛出异常@OverridepublicUserVoqueryUserInfo(UserParamuserParam)throwsBizException{thrownewBizException("6666","Testexceptionclass");}}我们定义一个全局的异常处理器,用@RestControllerAdvice注解,如下:@RestControllerAdvice(annotations=RestController.class)publicclassControllerExceptionHandler{}我们有要拦截的异常类型,比如拦截BizException类型,只需要添加一个new方法,并用@ExceptionHandler注解修改,如下:e){System.out.println("进入业务异常"+e.getRetCode()+e.getRetMessage());返回BaseResponse.fail(CodeEnum.ERROR.getCode(),CodeEnum.ERROR.getMessage());}}唠叨几句,这篇文章你学到了哪些知识?为了写出更优雅、更简洁、更易维护的代码,我们需要统一参数校验、统一响应对象返回、统一异常处理,让参数校验更简洁。这可以使用注释来实现。如何返回统一的响应对象,一般包括状态码、描述信息、返回数据。Controller层如何统一全局的异常处理?@RestControllerAdvice+@ExceptionHandler。进阶文章?您可以自己实现自定义注解。也推荐看一下@RestControllerAdvice的实现原理。它其实就是一个sectionannotation,看看它的源码就知道了。