在我们设计接口时,参数验证是必不可少的一个环节,严格的参数验证能够保证数据的严谨,那么在SpringBoot项目中,你是如何验证参数的呢?
首先我们来描述一下需求
用户类,有用户名、用户头像、邮件地址、年龄、手机号、出生日期,要求:
1、用户名不能为空且由字母数字下划线组成,不超过16个字符。
2、用户头像不能为空且为一个网络图片。
3、邮箱不能为空且必须为一个合法的邮件地址。
4、年龄不能为空且大于0。
5、手机号不能为空且必须合法。
6、出生日期不能为空且小于当前。
对于以上需求,你的验证方式是否如下呢
@PostMapping("/user/save")
public CommonResult save(@RequestBody UserReq req){
CommonResult r = CommonResult.success(null);
String username = req.getUsername();
String avatar = req.getAvatar();
Integer age = req.getAge();
LocalDate bothDate = req.getBothDate();
String email = req.getEmail();
String pattern = "[A-Za-z0-9_]+";
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(username);
if (!StringUtils.hasLength(username)) {
return CommonResult.error(400, "用户名岂能为空??");
} else if (m.matches()) {
return CommonResult.error(400, "用户名格式有误!");
} else if (username.length() > 16) {
return CommonResult.error(400, "用户名长度不大于16位");
}
if (!StringUtils.hasLength(avatar)) {
return CommonResult.error(400, "用户头像岂能为空??");
} else if (!avatar.startsWith("http://") && !avatar.startsWith("https://")) {
return CommonResult.error(400, "用户头像格式有误!");
}
...
// 剩余字段验证类似
r.setData(req);
return r;
}
上述代码可以实现各种场景的参数验证,但是仅仅两个字段已经写了20行代码,更不要说后台管理系统中复杂的参数验证了,那有些小伙伴可能会问,既然参数验证这么麻烦,那么能不能接口不验证,将这个麻烦的活交给前端去做呢?
有这个想法的小伙伴趁早打消念头哈,肯定是不行的,前端只能验证从我们系统前端发出的请求,如果有黑客绕过浏览器,使用http工具访问后台服务,并且有一些不好的企图,那么服务就危险了
那么今天我们就来说一说如何优雅的实现参数验证,跟if…else说拜拜吧
Spring已经整合了Hibernate Validator,对于SpringBoot来说使用也十分方便,使用 @Validated 注解,实现声明式校验
我们先认识一下相关注解
空和非空检查
数值检查
Boolean 值检查
长度检查
日期检查
其它检查
Hibernate Validator 附加的约束注解
org.hibernate.validator.constraints 包下,定义了一系列的约束( constraint )注解。如下:
下面就让我们来体验一下
1、首先导入依赖
小白记得在2.1.6版本的时候,这个依赖是不用导入的,但是具体在哪个版本,官方将这个依赖分离了出来,2.4.2需要手动导入
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
2、DTO添加参数验证注解
@Data
public class UserReq {
@NotBlank(message = "用户名岂能为空")
@Pattern(regexp = "[A-Za-z0-9_]+", message = "用户名格式有误")
@Size(max = 16, message = "用户名不能大于16位")
private String username;
@NotBlank(message = "用户头像岂能为空")
@Pattern(regexp = "[a-zA-z]+:\\/\\/[^\\s]*", message = "用户头像格式有误")
private String avatar;
@NotBlank(message = "邮箱岂能为空")
@Email(message = "邮箱格式有误")
private String email;
@NotNull(message = "年龄岂能为空")
@Min(value = 0, message = "年龄必须大于0")
private Integer age;
@NotBlank(message = "手机号岂能为空")
@Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手机号格式有误")
private String phone;
@NotNull(message = "出生日期岂能为空")
@Past(message = "出生日期不可大于当前")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate bothDate;
}
3、接口参数添加@Validated注解
@PostMapping("/user/save")
public CommonResult save(@RequestBody @Validated UserReq req){
CommonResult r = CommonResult.success(null);
r.setData(req);
return r;
}
4、添加全局异常处理器,处理参数验证异常
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
// 拼接错误
StringBuilder detailMessage = new StringBuilder();
for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
detailMessage.append(constraintViolation.getMessage());
}
return CommonResult.error(400, detailMessage.toString());
}
@ExceptionHandler(value = BindException.class)
public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {
// 拼接错误
StringBuilder detailMessage = new StringBuilder();
for (ObjectError objectError : ex.getAllErrors()) {
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
detailMessage.append(objectError.getDefaultMessage());
}
return CommonResult.error(400, detailMessage.toString());
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
// 拼接错误
StringBuilder detailMessage = new StringBuilder();
for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
detailMessage.append(objectError.getDefaultMessage());
}
return CommonResult.error(400, detailMessage.toString());
}
/**
* 处理其它 Exception 异常
*
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
return CommonResult.error(500, e.getMessage());
}
}
5、验证错误参数
6、验证正确参数
1、大多数场景下,我们使用 @Validated 注解即可。如果有嵌套校验的场景,使用 @Valid 注解添加到成员属性上。
2、保证接口的健壮性,参数验证是必不可少的环节,不可省略,而且应该极为严格,绝不能让恶意用户有可乘之机。
3、制作不易,一键三连再走吧,您的支持永远是我最大的动力!