在我们进行接口开发时,在对参数的接收,我们需要冗余复杂的校验规则 。那么如何优雅的实现参数的校验呢?
什么是不优雅的参数校验?
如果在控制层对前端传来的参数进行直接校验,那么避免不了许多的if-else的嵌套
如下(以添加用户的接口为例):
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("add")
public ResponseEntity<String> add(User user) {
if(user.getName()==null) {
return ResponseResult.fail("user name should not be empty");
} else if(user.getName().length()<5 || user.getName().length()>50){
return ResponseResult.fail("user name length should between 5-50");
}
if(user.getAge()< 1 || user.getAge()> 150) {
return ResponseResult.fail("invalid age");
}
// ...
return ResponseEntity.ok("success");
}
}
如何优雅的实现参数校验呢?
这里推荐一个好用的校验工具Spring Validation。
:::tips
Java API规范 (JSR303) 定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
:::
validator内置注解:
注解 | 详细信息 |
---|---|
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
Hibernate Validator 附加的 constraint:
注解 | 详细信息 |
---|---|
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
@NotBlank | 验证字符串非null,且长度必须大于0 |
实现步骤:
1.添加依赖
<!--参数校验相关-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.12.Final</version>
</dependency>
<!--参数校验相关-->
2.接口参数校验
对于web服务来说,为防止非法参数对业务造成影响,在Controller层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:
- POST、PUT请求,使用requestBody传递参数;
- GET请求,使用requestParam/PathVariable传递参数。
2.1 requestBody参数校验
POST、PUT请求一般会使用requestBody传递参数,这种情况下,后端使用DTO对象进行接收。只要给 DTO 对象加上@Validated注解就能实现自动参数校验。
:::tips
DTO 表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的。
:::
在DTO对象类中加入注解实现约束:
@Data
public class UserDTO implements Serializable {
private Long userId;
/**
* 用户名
*/
@NotNull(message = "用户名不能为空")
@Length(min = 2, max = 10,message = "用户名必须为2-10个字符")
private String username;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
private String email;
/**
* 电话
*/
@Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
@Pattern(regexp ="^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
private String phone;
/**
* 密码
*/
private String password;
}
在接口方法参数上声明校验注解:
@PutMapping("/api/save")
public Result add(@RequestBody @Validated UserDTO userDTO){
if(userDTO != null){
User user = new User();
BeanUtils.copyProperties(userDTO,user);
userSerive.insertUser(user);
return Result.success("保存成功");
}
throw new GlobalException("用户新增失败");
}
在全局异常处理类中加入参数异常的处理方法:
/**
* 控制器参数校验异常
* @param ex
* @return
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseBody
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
Map<String, Object> result = new HashMap<>();
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("参数校验异常 ");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(";");
}
String msg = sb.toString();
return Result.error(ApiCodeConstant.PARAMETER_VALIDATION_ERROR,msg);
}
/**
* Bean约束异常
* @param ex
* @return
*/
@ExceptionHandler({ConstraintViolationException.class})
@ResponseBody
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.error(ApiCodeConstant.PARAMETER_VALIDATION_ERROR,ex.getMessage());
}
测试结果:
2.2 requestParam/PathVariable参数校验
在控制器类上添加注解(@Validated):
@RequestParam: 通过键值对添加在路径中 通过这个注解可以映射至方法参数 (id=1)映射等号前面
:::tips
- 用于获取请求中的查询参数。
- 查询参数通常是以?key=value的形式出现在URL中,例如/api/user?id=1。
- 在方法参数中使用@RequestParam注解可以获取特定名称的查询参数的值。
- 使用该注解时,可以指定查询参数的名称和是否必需。如果不指定名称,则默认与方法参数名相同。
:::
@GetMapping("/api/get")
public Result getUserById( @Length(min = 6, max = 20) @NotNull @RequestParam("username") String username){
return Result.success("请求成功");
}
@PathVariable: 参数值直接作为路径的一部分 映射{ id} 的内容
:::tips
- 用于获取请求中的路径参数。
- 路径参数是将变量值直接包含在URL路径中的一部分,例如/api/user/{id}。
- 在方法参数中使用@PathVariable注解可以获取URL路径中的具体变量值。
- 使用该注解时,需要指定路径参数的名称,并且不能设置为可选。
:::
在定义 Restful 风格的接口时,通常会采用 PathVariable 指定关键业务参数,如下:
@GetMapping("/api/get/{username}")
public Result getUserById( @Length(min = 5, max = 20) @PathVariable("username") String username){
return Result.success("请求成功");
}