result = new HashMap<>();
result.put("fieldErrors", fieldErrors);
return RestResult.error(HttpStatus.UNPROCESSABLE_ENTITY.value(), "参数错误", result);
}
}
```
### 2.2 校验普通参数
##### 2.2.1 RequestParam校验
```
// 下面写法正确
// 校验失败会抛出 ConstraintViolationException 异常。
@GetMapping("/fun3")
public Object fun3(@Length(min = 5, max = 10) @NotNull String username) {
// 校验通过才会执行业务逻辑
return "ok";
}
// 下面的写法错误,不能加BindingResult
@GetMapping("/fun4")
public Object fun4(@Length(min = 5, max = 10) @NotNull String username, BindingResult bindingResult) {
return null;
}
```
##### 2.2.2 PathVariable校验
```
// 下面写法正确
// 校验失败会抛出 ConstraintViolationException 异常。
@GetMapping("/fun1/{userId}")
public Object fun1(@PathVariable @Min(1000L) Long userId) {
// 校验通过才会执行业务逻辑
return "ok";
}
// 下面的写法错误,不能加BindingResult
@GetMapping("/fun2/{userId}")
public Object fun2(@PathVariable @Min(1000L) Long userId, BindingResult bindingResult) {
return null;
}
```
### 2.3 数据校验+全局异常处理
cn.king.validation02.pojo.User 同上
cn.king.validation02.controller.AUserController
```
package cn.king.validation02.controller;
import cn.king.validation02.pojo.User;
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;
@RestController
@RequestMapping("/a/user")
public class AUserController {
/**
* 使用 @Valid 和 @Validated 都可以。
* RequestBody参数校验,校验失败会抛出 MethodArgumentNotValidException 异常。
*/
@PostMapping("/fun1")
public Object fun1(@RequestBody @Validated User user) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
}
```
cn.king.validation02.controller.BUserController
```
package cn.king.validation02.controller;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @author: wjl@king.cn
* @time: 2021/1/18 22:16
* @version: 1.0.0
* @description:
*/
@Validated
@RestController
@RequestMapping("/b/user")
public class BUserController {
// RequestMapping / PathVariable 参数校验。校验失败会抛出 ConstraintViolationException 异常。
@GetMapping("/fun2/{userId}")
public Object fun2(@PathVariable @Min(10000L) Long userId) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
// RequestMapping / PathVariable 参数校验。校验失败会抛出 ConstraintViolationException 异常。
@GetMapping("fun3")
public Object fun3(@Length(min = 5, max = 10) @NotNull String username) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
}
```
cn.king.validation02.exception.AGlobalExceptionHandler
```
package cn.king.validation02.exception;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 全局异常处理器
* AUserController、BUserController 如果校验失败,会抛出 MethodArgumentNotValidException 或者 ConstraintViolationException 异常。
* 在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。
* 比如我们系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。
*
* AGlobalExceptionHandler 和 BGlobalExceptionHandler 注释掉一个进行比较
*/
//@RestControllerAdvice
public class AGlobalExceptionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
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();
Map map = new HashMap<>();
map.put("code", -2);
map.put("msg", msg);
return map;
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handleConstraintViolationException(ConstraintViolationException ex) {
Map map = new HashMap<>();
map.put("code", -2);
map.put("msg", ex.getMessage());
return map;
}
}
```
cn.king.validation02.exception.BGlobalExceptionHandler
```
package cn.king.validation02.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
//@RestControllerAdvice
public class BGlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object notValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
log.info("请求的url为{}出现数据校验异常,异常信息为:", request.getRequestURI(), e);
BindingResult bindingResult = e.getBindingResult();
List errorMsgList = new ArrayList<>();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMsgList.add(fieldError.getDefaultMessage());
}
Map map = new HashMap<>();
map.put("code", 500);
map.put("msg", errorMsgList);
return map;
}
}
```
# 2.4 分组校验
cn.king.validation03.pojo.User
```
package cn.king.validation03.pojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @description: 有时候,为了区分业务场景,对于不同场景下的数据验证规则可能不一样(例如新增时可以不用传递 ID,而修改时必须传递ID),可以使用分组校验。
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
// groups:标识此校验规则属于哪个分组,可以指定多个分组
@NotNull(groups = Update.class)
@Min(value = 10000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 一个校验分组
* 保存的时候校验分组
*/
public interface Save {
// 校验分组中不需要定义任何方法,该接口仅仅是为了区分不同的校验规则
}
/**
* 一个校验分组
* 更新的时候校验分组
*/
public interface Update {
}
}
```
cn.king.validation03.controller.UserController
```
package cn.king.validation03.controller;
import cn.king.validation03.pojo.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public Object saveUser(@RequestBody @Validated(User.Save.class) User user) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
@PutMapping
public Object updateUser(@RequestBody @Validated(User.Update.class) User user) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
}
```
### 2.5 嵌套校验
cn.king.validation04.pojo.User
```
package cn.king.validation04.pojo;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @description: 如果POJO中包含了自定义的实体类,就需要用到嵌套校验。
* POJO中的某个字段也是一个对象,这种情况下,可以使用嵌套校验。
*/
@Data
public class User {
@Min(value = 1L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 此时DTO类的对应字段必须标记@Valid注解
*/
@Valid
@NotNull(groups = {Save.class, Update.class})
private Job job;
@Data
public static class Job {
@NotNull(groups = {Update.class})
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
}
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
}
```
cn.king.validation04.controller.UserController
```
package cn.king.validation04.controller;
import cn.king.validation04.pojo.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* @author: wjl@king.cn
* @time: 2021/1/21 21:49
* @version: 1.0.0
* @description:
*/
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public Object saveUser(@RequestBody @Validated(User.Save.class) User user) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
@PutMapping
public Object updateUser(@RequestBody @Validated(User.Update.class) User user) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
}
```
### 2.6 集合校验
cn.king.validation05.pojo.ValidationList
```
package cn.king.validation05.pojo;
import lombok.Data;
import lombok.experimental.Delegate;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* 如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。
* 此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:
*
* 包装 List类型,并声明 @Valid 注解
*/
@Data
public class ValidationList implements List {
@Delegate // @Delegate是lombok注解
@Valid // 一定要加@Valid注解
public List list = new ArrayList<>();
// 一定要记得重写toString方法
@Override
public String toString() {
return list.toString();
}
/*
* @Delegate注解受lombok版本限制,1.18.6以上版本可支持。
* 如果校验不通过,会抛出 NotReadablePropertyException, 同样可以使用统一异常进行处理。
*/
}
```
cn.king.validation05.pojo.User
```
package cn.king.validation05.pojo;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class User {
@NotNull
@Min(value = 1L)
private Long userId;
@NotNull
@Length(min = 2, max = 10)
private String userName;
@NotNull
@Length(min = 6, max = 20)
private String account;
@NotNull
@Length(min = 6, max = 20)
private String password;
}
```
cn.king.validation05.controller.ValidationListController
```
package cn.king.validation05.controller;
import cn.king.validation05.pojo.User;
import cn.king.validation05.pojo.ValidationList;
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;
@RestController
@RequestMapping("/api/valid/list")
public class ValidationListController {
@PostMapping
public Object saveList(@RequestBody @Validated ValidationList userList) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
}
```
### 2.7 1 Service层入参校验
Service层如何进行数据校验呢?
- 前台传来的参数已经在Controller中进行校验了,那么Service层方法的入参是不是就不需要进行校验了?看具体业务场景!如果前台传来的参数是加密的,到达Controller之后进行解密再传到Service,此时就需要校验Service的该入参。
- 我对于数据校验的理解是,只要数据可能不符合规范或者可能会出现空指针,那么该数据就必须进行校验,至于是直接抛异常还是返回null还是返回空集合,看具体业务来决定,如果抛出异常,就要进行好异常处理;如果返回null,就要在方法的调用者处进行好非空判断。
# 3 其他的数据校验框架
### 3.1 使用drools作为规则引擎