1.注解式校驗
1.1 常見校驗注解
定義的校驗類型
@Null 驗證對象是否為null
@NotNull 驗證對象是否不為null, 無法查檢長度為0的字符串
@NotBlank 檢查約束字符串是不是Null還有被Trim的長度是否大于0,只對字符串,且會去掉前后空格.
@NotEmpty 檢查約束元素是否為NULL或者是EMPTY.
@CreditCardNumber信用卡驗證
@Email 驗證是否是郵件地址,如果為null,不進(jìn)行驗證,算通過驗證。
@URL(protocol=,host=, port=,regexp=, flags=) ip地址校驗Booelan檢查
@AssertTrue 驗證 Boolean 對象是否為 true
@AssertFalse 驗證 Boolean 對象是否為 false長度檢查
@Size(min=, max=) 驗證對象(Array,Collection,Map,String)長度是否在給定的范圍之內(nèi)
@Length(min=, max=) Validates that the annotated string is between min and max included.日期檢查
@Past 驗證 Date 和 Calendar 對象是否在當(dāng)前時間之前
@Future 驗證 Date 和 Calendar 對象是否在當(dāng)前時間之后
@Pattern 驗證 String 對象是否符合正則表達(dá)式的規(guī)則數(shù)值檢查,建議使用在Stirng,Integer類型,不建議使用在int類型上,因為表單值為""時無法轉(zhuǎn)換為int,但可以轉(zhuǎn)換為Stirng為"",Integer為null
@Min 驗證 Number 和 String 對象是否大等于指定的值
@Max 驗證 Number 和 String 對象是否小等于指定的值
@DecimalMax 被標(biāo)注的值必須不大于約束中指定的最大值. 這個約束的參數(shù)是一個通過BigDecimal定義的最大值的字符串表示.小數(shù)存在精度
@DecimalMin 被標(biāo)注的值必須不小于約束中指定的最小值. 這個約束的參數(shù)是一個通過BigDecimal定義的最小值的字符串表示.小數(shù)存在精度
@Digits 驗證 Number 和 String 的構(gòu)成是否合法
@Digits(integer=,fraction=) 驗證字符串是否是符合指定格式的數(shù)字,interger指定整數(shù)精度,fraction指定小數(shù)精度。
@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.
1.2 實現(xiàn)簡單的注解式校驗栗子
1.2.1 請求實體
@Data
public class CommonReqVO<T> {
/**
* 互感器主鍵
*/
@NotNull(message = "業(yè)務(wù)類型不能為空")
private String type;
/**
* 請求數(shù)據(jù)
*/
@Valid
@NotNull(message = "數(shù)據(jù)不能為空")
private T data;
@NotNull(message = "開始時間不能為空!")
@Pattern(regexp = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[4]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$",message = "日期格式不正確")
// @DateTime(format = "yyyy-MM-dd", message = "時間格式錯誤,格式應(yīng)為'yyyy-MM-dd'")
private String startDate;
}
@Data
public class UserVO {
@NotBlank(message = "name不能為空",groups = S1.class)
private String name;
@Range(min = 1,max = 120,message = "年齡必須在1-120之間",groups = S1.class)
private Integer age;
@NotBlank(message = "address不能為空",groups = S2.class)
private String address;
@NotBlank(message = "phone不能為空",groups = S2.class)
private String phone;
public interface S1{}
public interface S2{}
}
1.2.2 controller
@RestController
@RequestMapping("test")
public class TestController {
@PostMapping("t3")
public String test3(@RequestBody CommonReqVO<UserVO> userVO){
ValidateUtil.validateParams(userVO,"請求參數(shù)不能為空");
if ("01".equals(userVO.getType())){
ValidateUtil.validateParams(userVO,"請求參數(shù)不能為空",UserVO.S1.class);
}else if("02".equals(userVO.getType())){
ValidateUtil.validateParams(userVO,"請求參數(shù)不能為空",UserVO.S2.class);
}else {
throw new CustomException("請求參數(shù)type類型不匹配!");
}
return "t3";
}
@PostMapping("t4")
public String test4(@RequestBody @Validated CommonReqVO<UserVO> userVO){
return "4";
}
}
1.2.3 統(tǒng)一異常處理類及相關(guān)類
@RestControllerAdvice
@Slf4j
public class ExceptionHandle {
@Value("${validator.showField:false}")
private Boolean showFile;
@ExceptionHandler(CustomException.class)
public ResultInfo handleCustomException(CustomException e){
log.error("【用戶自定義異?!縶}", e);
return ResultInfoUtil.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResultInfo handleReqException(HttpMediaTypeNotSupportedException e){
log.error("【請求類型不支持異?!縶}", e);
if (e.getMessage().contains("not supported")){
return ResultInfoUtil.error(0, "請求Content-Type不匹配");
}
return ResultInfoUtil.error(0, e.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResultInfo handleReqBodyException(HttpMessageNotReadableException e){
log.error("【請求體異?!縶}", e);
if (e.getMessage().contains("Required request body is missing")){
return ResultInfoUtil.error(0, "請求體不能為空");
}
return ResultInfoUtil.error(0, e.getMessage());
}
@ExceptionHandler(Exception.class)
public ResultInfo handleOtherException(Exception e){
log.error("【系統(tǒng)異常】{}", e);
return ResultInfoUtil.error(0, e.getMessage());
}
/**
* 處理validation框架中的{@link MethodArgumentNotValidException}異常
* <p>該異常一般出在用{@code @RequestBody}注解標(biāo)記的參數(shù)校驗,在對象中的參數(shù)有問題是拋出</p>
*
* @param e {@link MethodArgumentNotValidException}
* @return {@link ResultInfo}
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultInfo doNoValidExceptionHandler(MethodArgumentNotValidException e) {
log.info("捕捉到參數(shù)校驗異常: [{}]", e);
return validationExceptionHandler(e.getBindingResult(), e.getMessage());
}
/**
* 處理validation框架中的{@link ConstraintDeclarationException}異常
* <p>該異常一般出在用{@code @RequestParam}注解標(biāo)記的參數(shù)校驗</p>
*
* @param e {@link ConstraintDeclarationException}
* @return {@link ResultInfo}
*/
@ExceptionHandler(ConstraintDeclarationException.class)
public ResultInfo doNoValidExceptionHandler(ConstraintDeclarationException e) {
String errorMsg = e.getMessage();
log.info("捕捉到參數(shù)校驗異常: [{}]", errorMsg, e);
return ResultInfoUtil.error(0, e.getMessage());
}
/**
* 處理validation框架中的{@link BindException}異常
* <p>該異常一般出在用{@code @RequestBody}注解標(biāo)記的參數(shù)校驗,在對象嵌套類型參數(shù)有問題時拋出</p>
*
* @param e {@link BindException}
* @return {@link ResultInfo}
*/
@ExceptionHandler(BindException.class)
public ResultInfo doNoValidExceptionHandler(BindException e) {
log.info("捕捉到參數(shù)校驗異常: [{}]", e);
return validationExceptionHandler(e.getBindingResult(), e.getMessage());
}
/**
* 處理validation框架的異常
*
* @param bindingResult {@link BindingResult} 異常綁定的對象信息
* @param message 異常信息
* @return {@link ResultInfo}
*/
private ResultInfo validationExceptionHandler(BindingResult bindingResult, String message) {
//默認(rèn)捕獲第一個不符合校驗規(guī)則的錯誤信息
//錯誤字段對象
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
List<String> errorMessages = new ArrayList<>(fieldErrors.size());
if (showFile){
fieldErrors.stream().forEach(e->{
errorMessages.add(e.getField()+"-"+e.getDefaultMessage());
});
}else {
fieldErrors.stream().forEach(e->{
errorMessages.add(e.getDefaultMessage());
});
}
log.info("捕捉到參數(shù)校驗異常詳情: {}", errorMessages);
return ResultInfoUtil.error(0, errorMessages.toString());
}
}
@Data
public class ResultInfo<T> {
/**
*返回碼
*/
private Integer code;
/**
*返回提示信息
*/
private String msg;
/**
*返回具體對象
*/
private T data;
}
public class ResultInfoUtil {
public static ResultInfo success(Object object) {
ResultInfo result = new ResultInfo();
ResultInfoEnum successEnum = ResultInfoEnum.SUCCESS;
result.setCode(successEnum.getCode());
result.setMsg(successEnum.getMsg());
result.setData(object);
return result;
}
public static ResultInfo success() {
return success(null);
}
public static ResultInfo error(Integer code, String msg) {
ResultInfo result = new ResultInfo();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
@Data
public class CustomException extends RuntimeException {
private Integer code;
public CustomException(ResultInfoEnum resultInfoEnum) {
super(resultInfoEnum.getMsg());
this.code = resultInfoEnum.getCode();
}
public CustomException(String msg) {
super(msg);
this.code = ResultInfoEnum.FAILED.getCode();
}
}
注意上面配置了 failFast 為false。所以會校驗全部參數(shù)
1.2.4 請求及響應(yīng)
請求
{}
響應(yīng)
{
"code": 0,
"msg": "[data-數(shù)據(jù)不能為空, type-業(yè)務(wù)類型不能為空, startDate-開始時間不能為空!]",
"data": null
}
2.編程式校驗
2.1 校驗相關(guān)類
@Configuration
public class ValidateUtil<T> {
private static Validator validator;
/**
* 是否啟用快速失敗
*/
private static Boolean failFast;
/**
* 是否顯示錯誤字段
*/
private static Boolean showField;
public static void setFailFast(Boolean failFast) {
ValidateUtil.failFast = failFast;
}
public static void setShowField(Boolean showField) {
ValidateUtil.showField = showField;
}
/**
* @param obj 校驗的對象
* @param <T>
* @return
*/
public static <T> Set<ConstraintViolation<T>> validate(T obj, Class... groupClasses) {
Set<ConstraintViolation<T>> constraintViolations;
if (groupClasses != null && groupClasses.length > 0) {
constraintViolations = validator.validate(obj, groupClasses);
} else {
constraintViolations = validator.validate(obj);
}
return constraintViolations;
}
/**
* 驗證請求參數(shù)是否為空
*
* @param o 參數(shù)對象
* @param msg 報錯信息
*/
public static void checkIsNotNull(Object o, String msg) {
if (o == null) {
throw new CustomException(msg);
}
}
public static <T> void validateParams(T t, String msg, Class... groupClasses) {
checkIsNotNull(t, msg);
Set<ConstraintViolation<T>> constraintViolationSet = validate(t, groupClasses);
List<String> resultString = new ArrayList<>(constraintViolationSet.size());
if (!constraintViolationSet.isEmpty()) {
if (showField){
constraintViolationSet.stream().forEach(c -> {
resultString.add(c.getPropertyPath().toString() + "-" + c.getMessage());
});
}else {
constraintViolationSet.stream().forEach(c -> {
resultString.add(c.getMessage());
});
}
throw new CustomException(resultString.toString());
}
}
/**
* 注入validator
*
* @param validatorBean
*/
public static void setValidator(Validator validatorBean) {
validator = validatorBean;
}
}
@Configuration
public class ValidatorConfiguration {
@Value("${validator.failFast:true}")
private Boolean failFast;
@Value("${validator.showField:false}")
private Boolean showField;
@Autowired
private ServletContext servletContext;
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失敗
.failFast(failFast)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
/**
* 給ValidateUtil初始化
*/
@PostConstruct
public void initValidateUtil() {
ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
Validator validator = (Validator) applicationContext.getBean("validator");
ValidateUtil.setValidator(validator);
ValidateUtil.setFailFast(failFast);
ValidateUtil.setShowField(showField);
}
}
2.2 配置
#校驗相關(guān)配置
validator:
#快速失敗
failFast: true
#錯誤提示是否顯示錯誤字段
showField: false
3.自定義校驗注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateTime.DateTimeValidator.class)
public @interface DateTime {
String message() default "格式錯誤";
String format() default "yyyyMM";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class DateTimeValidator implements ConstraintValidator<DateTime, String> {
private DateTime dateTime;
@Override
public void initialize(DateTime dateTime) {
this.dateTime = dateTime;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果 value 為空則不進(jìn)行格式驗證,為空驗證可以使用 @NotBlank @NotNull @NotEmpty 等注解來進(jìn)行控制,職責(zé)分離
if (value == null) {
return true;
}
String format = dateTime.format();
if (value.length() != format.length()) {
return false;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
simpleDateFormat.parse(value);
} catch (Exception e) {
return false;
}
return true;
}
}
}
3.1 使用方法
@DateTime(format = "yyyy-MM-dd", message = "時間格式錯誤,格式應(yīng)為'yyyy-MM-dd'")
private String startDate;