当前位置:网站首页>优雅书写Controller(参数验证+统一异常处理)
优雅书写Controller(参数验证+统一异常处理)
2022-06-29 17:59:00 【李奈 - Leemon】
优雅书写Controller(参数验证+统一异常处理)
文章目录
最近开发了比较多的接口,因为没有可参考的案例,所以一开始一直按照我的理解进行开发。开发多了发现自己每个结果都写了相同的代码:try() {} catch() {}, 和关于参数判空的:StringUtils.empty(xxx)。秉着饱暖思淫欲的态度,开发结束后自然想下次更加优雅的开发。因此,使用了springboot的参数验证和统一异常处理。
一,前期数据及类准备
1.1 统一状态码
对于不同的返回类型,我们应该要有不同对应的状态码。接口的返回类型在统一状态码中必须存在。
package com.lmc.common.enums;
/** * @author lmc * @Description: TODO 接口API返回状态码枚举 * @Create 2022-06-26 13:16 * @version: 1.0 */
public enum ResultCodeEnum {
SUCCESS(1000, "请求成功"),
FAILURE(1001, "请求失败"),
VALIDATE_PARAMS_ERROR(1002, "参数校验失败");
private int code;
private String msg;
ResultCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
/** * 获取code * @return */
public int getCode() {
return code;
}
/** * 获取信息 * @return */
public String getMsg() {
return msg;
}
}
1.2 统一返回格式
统一状态码完成后,还需要定义统一返回格式,为了前端的方便调用
package com.lmc.common.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.lmc.common.enums.ResultCodeEnum;
import lombok.Data;
import java.util.Date;
/** * @author lmc * @Description: TODO 接口返回结果类型 * @Create 2022-06-26 13:13 * @version: 1.0 */
@Data
public class ResultVo {
/** * 状态码 */
private int code;
/** * 状态码信息 */
private String msg;
/** * 返回描述信息(预备为调用失败的情况下提供详细的失败原因) */
private String desc;
/** * 返回数据 */
private Object data;
/** * 接口调用结束时间 */
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
private Date searchTime;
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
this.searchTime = new Date();
}
/** * 调用成功时返回 * @param data * @return */
public static ResultVo success(Object data) {
return new ResultVo(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMsg(), data);
}
/** * 调用失败时返回 * @param data * @return */
public static ResultVo fail(Object data) {
return new ResultVo(ResultCodeEnum.FAILURE.getCode(), ResultCodeEnum.FAILURE.getMsg(), data);
}
/** * 调用时指定状态码 * @param enums * @param data * @return */
public static ResultVo result(ResultCodeEnum enums, Object data) {
return new ResultVo(enums.getCode(), enums.getMsg(), data);
}
public ResultVo withDesc(String desc) {
this.desc = desc;
return this;
}
}
1.3 自定义接口API异常类
然后再自定义接口的异常类,当然也可以不用,看个人喜好
package pers.lmc.tools2.provider.exception;
import com.lmc.common.enums.ResultCodeEnum;
/** * @author lmc * @Description: TODO API异常类 * @Create 2022-06-26 18:48 * @version: 1.0 */
public class ApiException extends RuntimeException{
private int code;
private String msg;
public ApiException(String msg) {
super(msg);
this.code = ResultCodeEnum.FAILURE.getCode();
this.msg = ResultCodeEnum.FAILURE.getMsg();
}
public ApiException(ResultCodeEnum enums, String msg) {
super(msg);
this.code = enums.getCode();
this.msg = enums.getMsg();
}
}
1.4 参数封装类
为了调试参数验证,还需要自定义一个参数的封装类
package pers.lmc.tools2.provider.vo;
import lombok.Data;
/** * @author lmc * @Description: TODO * @Create 2022-06-26 13:43 * @version: 1.0 */
@Data
public class Param01Vo {
private String name;
private Integer age;
private Short sex;
}
二,参数验证
参数验证需要用到springboot的validation依赖
2.1 pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 关于校验 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
2.2 修改参数封装类
package pers.lmc.tools2.provider.vo;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/** * @author lmc * @Description: TODO * @Create 2022-06-26 13:43 * @version: 1.0 */
@Data
public class Param01Vo {
@NotNull(message = "名称不能为空")
@Size(min = 1, max = 50, message = "名称name长度必须是1-50个字符")
private String name;
@NotNull(message = "年龄age不能为空")
@Min(value = 10, message = "年龄age不能低于10岁")
@Max(value = 25, message = "年龄age不能超过25岁")
private Integer age;
@Min(value = 0, message = "性别sex只能是0和1,0=女1=男")
@Max(value = 1, message = "性别sex只能是0和1,0=女1=男")
private Short sex;
}
在这里对该封装类的三个参数都做了限制
2.3 controller
在controller中对参数做验证时,需要在类上使用注解@Validated,同时在接口的该参数也使用注解@Valid
package pers.lmc.tools2.provider.controller;
import com.lmc.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.lmc.tools2.provider.vo.Param01Vo;
import javax.validation.Valid;
/** * @author lmc * @Description: TODO * @Create 2022-06-26 17:10 * @version: 1.0 */
@RestController
@Validated
@RequestMapping("/valicate")
@Slf4j
public class ValicateController {
@PostMapping("/add")
public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) {
log.info("执行add()方法,参数:" + param01Vo.toString());
return ResultVo.success(param01Vo);
}
}
2.4 测试
开发完成,准备测试,到APIPost上访问 http://localhost:9003/provider/valicate/add,带上参数:
{
"name":"lmc",
"age": 22,
"sex": 1
}
访问成功,返回结果如下:
{
"code": 1000,
"msg": "请求成功",
"desc": null,
"data": {
"name": "lmc",
"age": 22,
"sex": 1
},
"searchTime": "2022-06-26 19:59:55"
}
如果参数输入不正确,例如:
{
"name":"",
"age": 220,
"sex": 2
}
得到结果如下:
{
"timestamp": "2022-06-26T12:02:21.748+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/provider/valicate/add"
}
日志是这样的:
2022-06-26 20:02:21 [http-nio-9003-exec-1] WARN o.s.w.s.m.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.lmc.common.vo.ResultVo pers.lmc.tools2.provider.controller.ValicateController.addParam01(pers.lmc.tools2.provider.vo.Param01Vo) with 3 errors: [Field error in object 'param01Vo' on field 'sex': rejected value [2]; codes [Max.param01Vo.sex,Max.sex,Max.java.lang.Short,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.sex,sex]; arguments []; default message [sex],1]; default message [性别sex只能是0和1,0=女1=男]] [Field error in object 'param01Vo' on field 'age': rejected value [220]; codes [Max.param01Vo.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.age,age]; arguments []; default message [age],25]; default message [年龄age不能超过25岁]] [Field error in object 'param01Vo' on field 'name': rejected value []; codes [Size.param01Vo.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.name,name]; arguments []; default message [name],50,1]; default message [名称name长度必须是1-50个字符]] ]
抛出了MethodArgumentNotValidException异常。
虽然参数错误时确实被拦截了,但格式已经和我们想要返回的不一致了。这个时候,就需要用到统一异常处理了。
三,统一异常处理
3.1 方法参数验证异常处理
通过以上的问题,我们可以设置controller的统一异常处理,当出现参数验证错误时,就捕获MethodArgumentNotValidException异常,然后我们自己做处理。
package pers.lmc.tools2.provider.aop;
import com.lmc.common.enums.ResultCodeEnum;
import com.lmc.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pers.lmc.tools2.provider.exception.ApiException;
import java.util.List;
import java.util.stream.Collectors;
/** * @author lmc * @Description: TODO * @Create 2022-06-26 18:31 * @version: 1.0 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/** * 处理所有校验失败的异常(MethodArgumentNotValidException异常) * @param e * @return */
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResultVo handleBindGetException(MethodArgumentNotValidException e) {
// 获取所有异常参数
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(x -> x.getDefaultMessage())
.collect(Collectors.toList());
return ResultVo.result(ResultCodeEnum.VALIDATE_PARAMS_ERROR, null).withDesc("参数校验失败:" + errors);
}
/** * 处理自定义APIException异常 * @param e * @return */
@ExceptionHandler(value = ApiException.class)
public ResultVo handleApiException(ApiException e) {
return ResultVo.fail(null).withDesc(e.getMessage());
}
/** * 处理其他异常 * @param e * @return */
@ExceptionHandler(value = Exception.class)
public ResultVo handleException(Exception e) {
log.info("执行到统一处理方法...");
return ResultVo.fail(null).withDesc(e.getMessage());
}
}
通过以上配置,再次以非法参数传输时,会报出以下错误:
{
"code": 1002,
"msg": "参数校验失败",
"desc": "参数校验失败:[性别sex只能是0和1,0=女1=男, 名称name长度必须是1-50个字符, 年龄age不能超过25岁]",
"data": null,
"searchTime": "2022-06-26 20:08:22"
}
这个时候格式已经我们想要的返回格式了。
3.2 其他异常处理
刚刚我们尝试的是方法的参数验证异常的处理,对于程序还可能出现的错误,配置统一异常处理后也不需要使用try{} catch() {},因为我们已经在全局异常处理类中配置了:
/** * 处理其他异常 * @param e * @return */
@ExceptionHandler(value = Exception.class)
public ResultVo handleException(Exception e) {
log.info("执行到统一处理方法...");
return ResultVo.fail(null).withDesc(e.getMessage());
}
这个时候在程序中抛出其他异常,就会执行到这里的代码,同样返回我们想要的格式。举例如下
修改controller接口:
@PostMapping("/add")
public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) {
log.info("执行add()方法,参数:" + param01Vo.toString());
int k = 1/0; // 调用该接口时执行到这里会抛出异常
return ResultVo.success(param01Vo);
}
调用接口返回结果:
{
"code": 1001,
"msg": "请求失败",
"desc": "/ by zero",
"data": null,
"searchTime": "2022-06-26 20:13:51"
}
边栏推荐
- 【目标跟踪】|stark配置 win otb
- Adobe Premiere基础-素材嵌套(制作抖音结尾头像动画)(九)
- If the evaluation conclusion of waiting insurance is poor, does it mean that waiting insurance has been done in vain?
- C comparison of the performance of dapper efcore sqlsugar FreeSQL hisql sqlserver, an ORM framework at home and abroad
- PWM output experiment based on stm32f103zet6 library function
- When easycvr deploys a server cluster, what is the reason why one is online and the other is offline?
- Niuke Xiaobai monthly race 52 E group logarithmic sum (inclusion exclusion theorem + dichotomy)
- How do I add SmartArt to slides in PowerPoint?
- Maximum length of palindrome substring (string hash + binary)
- Adobe Premiere基础-不透明度(混合模式)(十二)
猜你喜欢

What technology is an applet container? Can it help Internet of things enterprises break through the red sea?

Two controller layer interface authentication methods

Parental delegation mechanism

Detailed introduction and Simulation of bitmap

Adobe Premiere基础-炫酷文字快闪(十四)

Spingmvc requests and responses

Xiaobai yuesai 51 supplement e g f

Issue 42: is it necessary for MySQL to have multiple column partitions

Serial port experiment based on stm32f103zet6 library function

Proxmox VE Install 7.2
随机推荐
What technology is an applet container? Can it help Internet of things enterprises break through the red sea?
Two controller layer interface authentication methods
shell教程之循环语句for,while,until用法
jdbc_ Related codes
金鱼哥RHCA回忆录:DO447构建高级作业工作流--使用事实缓存提高性能
Error building SqlSession问题
Adobe Premiere Foundation - réglage du son (correction du volume, réduction du bruit, tonalité téléphonique, changement de hauteur, égaliseur de paramètres) (XVIII)
Proxmox VE Install 7.2
Proxmox VE Install 7.2
Shell tutorial circular statements for, while, until usage
Niuke small Bai monthly race 52 D ring insectivorous (feet +st table)
3H proficient in opencv (VIII) - shape detection
自动化软件测试 - 利用短信转发器结合Selenium读取短信验证码
How QQ opens online customer service
Image feature computation and representation -- content based image retrieval
The soft youth under the blessing of devcloud makes education "smart" in the cloud
Adobe Premiere foundation - material nesting (animation of Tiktok ending avatar) (IX)
[tcapulusdb knowledge base] tcapulusdb system user group introduction
Yolov6+tensorrt+onnx: deployment based on win10+tensorrt8+yolov6+onnx
【TcaplusDB知识库】TcaplusDB单据受理-建表审批介绍