目录
1.JSR303
2.依赖
3.注解
4.实现
编辑4.1简单校验
4.2 统一异常处理
4.3 使用枚举返回分组的业务状态码
4.4 JSR303提供的分组校验功能(推荐)
4.5 自定义校验(推荐)
日常开发的业务中难免要遇到对于数据的校验,比如:登陆注册,手机号是否规范、密码长度是否符合、账号不能为空等等,而如果我们在controller中使用if-else来实现代码即多逻辑又复杂,所以我们可以使用JSR303来实现这个过程。
JSR是Java Specification Requests的缩写,意思是Java 规范提案
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,即JSR 303 Bean Validation规范 ,为Bean验证定义了元数据模型和API。默认的元数据模型是通过Annotations来描述的,但是也可以使用XML来重载或者扩展。
org.springframework.boot
spring-boot-starter-validation
2.3.2.RELEASE
javax.validation
validation-api
2.0.1.Final
springboot项目不需要导入这个依赖。
源文件:
javax.validation.constraints.AssertFalse.message = must be false
javax.validation.constraints.AssertTrue.message = must be true
javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Email.message = must be a well-formed email address
javax.validation.constraints.Future.message = must be a future date
javax.validation.constraints.FutureOrPresent.message = must be a date in the present or in the future
javax.validation.constraints.Max.message = must be less than or equal to {value}
javax.validation.constraints.Min.message = must be greater than or equal to {value}
javax.validation.constraints.Negative.message = must be less than 0
javax.validation.constraints.NegativeOrZero.message = must be less than or equal to 0
javax.validation.constraints.NotBlank.message = must not be blank
javax.validation.constraints.NotEmpty.message = must not be empty
javax.validation.constraints.NotNull.message = must not be null
javax.validation.constraints.Null.message = must be null
javax.validation.constraints.Past.message = must be a past date
javax.validation.constraints.PastOrPresent.message = must be a date in the past or in the present
javax.validation.constraints.Pattern.message = must match "{regexp}"
javax.validation.constraints.Positive.message = must be greater than 0
javax.validation.constraints.PositiveOrZero.message = must be greater than or equal to 0
javax.validation.constraints.Size.message = size must be between {min} and {max}
org.hibernate.validator.constraints.CreditCardNumber.message = invalid credit card number
org.hibernate.validator.constraints.Currency.message = invalid currency (must be one of {value})
org.hibernate.validator.constraints.EAN.message = invalid {type} barcode
org.hibernate.validator.constraints.Email.message = not a well-formed email address
org.hibernate.validator.constraints.ISBN.message = invalid ISBN
org.hibernate.validator.constraints.Length.message = length must be between {min} and {max}
org.hibernate.validator.constraints.CodePointLength.message = length must be between {min} and {max}
org.hibernate.validator.constraints.LuhnCheck.message = the check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod10Check.message = the check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod11Check.message = the check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
org.hibernate.validator.constraints.ModCheck.message = the check digit for ${validatedValue} is invalid, {modType} checksum failed
org.hibernate.validator.constraints.Normalized.message = must be normalized
org.hibernate.validator.constraints.NotBlank.message = may not be empty
org.hibernate.validator.constraints.NotEmpty.message = may not be empty
org.hibernate.validator.constraints.ParametersScriptAssert.message = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.Range.message = must be between {min} and {max}
org.hibernate.validator.constraints.ScriptAssert.message = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.UniqueElements.message = must only contain unique elements
org.hibernate.validator.constraints.URL.message = must be a valid URL
org.hibernate.validator.constraints.br.CNPJ.message = invalid Brazilian corporate taxpayer registry number (CNPJ)
org.hibernate.validator.constraints.br.CPF.message = invalid Brazilian individual taxpayer registry number (CPF)
org.hibernate.validator.constraints.br.TituloEleitoral.message = invalid Brazilian Voter ID card number
org.hibernate.validator.constraints.pl.REGON.message = invalid Polish Taxpayer Identification Number (REGON)
org.hibernate.validator.constraints.pl.NIP.message = invalid VAT Identification Number (NIP)
org.hibernate.validator.constraints.pl.PESEL.message = invalid Polish National Identification Number (PESEL)
org.hibernate.validator.constraints.ru.INN.message = invalid Russian taxpayer identification number (INN)
org.hibernate.validator.constraints.time.DurationMax.message = must be shorter than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
org.hibernate.validator.constraints.time.DurationMin.message = must be longer than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
常用:
javax.validation.constraints.AssertFalse.message = must be false
javax.validation.constraints.AssertTrue.message = must be true
javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Email.message = must be a well-formed email address
javax.validation.constraints.Future.message = must be a future date
javax.validation.constraints.FutureOrPresent.message = must be a date in the present or in the future
javax.validation.constraints.Max.message = must be less than or equal to {value}
javax.validation.constraints.Min.message = must be greater than or equal to {value}
javax.validation.constraints.Negative.message = must be less than 0
javax.validation.constraints.NegativeOrZero.message = must be less than or equal to 0
javax.validation.constraints.NotBlank.message = must not be blank
javax.validation.constraints.NotEmpty.message = must not be empty
javax.validation.constraints.NotNull.message = must not be null
javax.validation.constraints.Null.message = must be null
javax.validation.constraints.Past.message = must be a past date
javax.validation.constraints.PastOrPresent.message = must be a date in the past or in the present
javax.validation.constraints.Pattern.message = must match "{regexp}"
javax.validation.constraints.Positive.message = must be greater than 0
javax.validation.constraints.PositiveOrZero.message = must be greater than or equal to 0
javax.validation.constraints.Size.message = size must be between {min} and {max}
我尽力了。。。。。。。
这个是扒拉的其他博主的图片:JSP-303校验_地中海未来的博客-CSDN博客
实体类
(在字段上加上相对应的注解)
controller层
(加上BindingResult类,异常处理)
@PostMapping("/save")
// @RequiresPermissions("product:pmsbrand:save")
public R save(@Valid @RequestBody PmsBrandEntity pmsBrand, BindingResult result){
if(result.hasErrors()){
Map map = new HashMap<>();
result.getFieldErrors().forEach((item) ->{
//获取错误提示
String message = item.getDefaultMessage();
//获取错误属性名
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else {
}
pmsBrandService.save(pmsBrand);
return R.ok();
}
apifox测试
实体类层属性加上相对应的注解,controller层只加上@Valid注解即可。
在exception包里面创建一个统一异常处理类。
import com.guli.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* @author 86150
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.guli.product.controller")
public class ControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e) {
log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError -> {
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
}));
return R.error(400, "数据校验出现问题").put("data",errorMap);
}
//其他的除了上面数据校验的异常处理,可能会发生的其他异常
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error();
}
}
测试
基本过程与4.2相同,只是多加了一个枚举类,统一异常处理类进行了一些修改。
枚举类:
public enum BizCodeEnume {
VAILD_EXCEPTION(10001,"参数格式校验失败"),
SMS_CODE_EXCEPTION(10002,"发送验证码太频繁,请稍后再试"),
TOO_MANY_REQUEST(10003,"请求流量过大"),
UNKNOW_EXCEPTION(10000,"系统未知异常"),
PRODUCT_UP_EXCEPTION(11000, "商品上架异常"),
USER_EXIST_EXCEPTION(15001,"用户已存在"),
PHONE_EXIST_EXCEPTION(15002,"手机号码已存在"),
PHONE_NULL_EXCEPTION(15003,"未输入手机号码"),
NO_STOCK_EXCEPTION(21000,"商品库存不足"),
LOGINACCT_PASSWORD_INVAILD_EXCEPTION(15002,"账号或密码错误");
private int code;
private String msg;
BizCodeEnume(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
统一异常处理类
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* @author 86150
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.guli.product.controller")
public class ControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e) {
log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError -> {
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
}));
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data", errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable) {
log.error("错误", throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(), BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
创建一个valid包,包下为:
public interface AddGroup {
}
public interface UpdateGroup {
}
实体类
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定Id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定Id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotEmpty(message="品牌名不能为空",groups = {UpdateGroup.class,AddGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotEmpty(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址",groups = {UpdateGroup.class,AddGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
}
controller层得用@Validated注解,同时加上分组
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
return R.ok();
}
可以根据其中一个已经写好了的比如@NotBlank来比葫芦画瓢来自定义一个校验。
栗子:修改status(status只能为0或1)
导入依赖:
javax.validation
validation-api
2.0.1.Final
valid包下加入:
package com.guli.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(
validatedBy = {ListValueConstraintValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
String message() default "{com.guli.common.valid.ListValue.message}";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
int[] vals() default {};
}
package com.guli.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class ListValueConstraintValidator implements ConstraintValidator {
private Set set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for(int val : vals){
set.add(val);
}
}
//判断是否校验成功
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
package com.guli.common.valid;
public interface UpdateStatusGroup {
}
com.guli.common.valid.ListValue.message=必须只能指定是1或者0
实体类
package com.guli.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.guli.common.valid.AddGroup;
import com.guli.common.valid.ListValue;
import com.guli.common.valid.UpdateGroup;
import com.guli.common.valid.UpdateStatusGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
/**
* 品牌
*
* @author yang
* @email [email protected]
* @date 2023-05-04 22:36:08
*/
@Data
@TableName("pms_brand")
public class PmsBrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定Id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定Id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotEmpty(message="品牌名不能为空",groups = {UpdateGroup.class,AddGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotEmpty(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址",groups = {UpdateGroup.class,AddGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(message = "必须只能指定是1或者0",vals = {0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups = {AddGroup.class, UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
public Long getBrandId() {
return brandId;
}
public void setBrandId(Long brandId) {
this.brandId = brandId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
public String getDescript() {
return descript;
}
public void setDescript(String descript) {
this.descript = descript;
}
public Integer getShowStatus() {
return showStatus;
}
public void setShowStatus(Integer showStatus) {
this.showStatus = showStatus;
}
public String getFirstLetter() {
return firstLetter;
}
public void setFirstLetter(String firstLetter) {
this.firstLetter = firstLetter;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
}
controllet层
/**
* 保存
*/
@PostMapping("/save")
// @RequiresPermissions("product:pmsbrand:save")
public R save(@Validated({AddGroup.class}) @RequestBody PmsBrandEntity pmsBrand){
pmsBrandService.save(pmsBrand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
// @RequiresPermissions("product:pmsbrand:update")
public R update(@RequestBody PmsBrandEntity pmsBrand){
pmsBrandService.updateById(pmsBrand);
return R.ok();
}
测试: