@Validated和@Valid的區(qū)別
在Controller中校驗(yàn)方法參數(shù)時(shí)盛嘿,使用@Valid和@Validated并無(wú)特殊差異(若不需要分組校驗(yàn)的話)
@Valid:標(biāo)準(zhǔn)JSR-303規(guī)范的標(biāo)記型注解,用來(lái)標(biāo)記驗(yàn)證屬性和方法返回值凯亮,進(jìn)行級(jí)聯(lián)和遞歸校驗(yàn)
@Validated:Spring的注解返奉,是標(biāo)準(zhǔn)JSR-303的一個(gè)變種(補(bǔ)充)尤蒿,提供了一個(gè)分組功能,可以在入?yún)Ⅱ?yàn)證時(shí)萍嬉,根據(jù)不同的分組采用不同的驗(yàn)證機(jī)制
方法級(jí)別:
@Validated注解可以用于類級(jí)別乌昔,用于支持Spring進(jìn)行方法級(jí)別的參數(shù)校驗(yàn)。@Valid可以用在屬性級(jí)別約束帚湘,用來(lái)表示級(jí)聯(lián)校驗(yàn)玫荣。
@Validated只能用在類、方法和參數(shù)上大诸,而@Valid可用于方法捅厂、字段、構(gòu)造器和參數(shù)上
如何使用
這兩個(gè)包要同時(shí)導(dǎo)入资柔!
<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.7.Final</version>
</dependency>
@PostMapping("/tabletSign/pushInfo/patient")
public AjaxResult pushInfoPatient(@Valid @RequestBody BizPatient bizPatient) {
}
public class BizPatient {
private static final long serialVersionUID = 1L;
@NotNull(message = "id不能為空")
private Long patientId;
}
如何分組校驗(yàn)焙贷?
有時(shí)候我們需要在不同的Controller中校驗(yàn)不同的字段
Controller
@PostMapping("/tabletSign/pushInfo/patient")
public AjaxResult pushInfoPatient(
@Validated(BizPatient.SaveGroup.class) @RequestBody BizPatient bizPatient) {
}
@PostMapping("/tabletSign/patient/signerInfo")
public AjaxResult getSignerInfo(
@Validated(BizPatient.SelectGroup.class) @RequestBody BizPatient bizPatient) {
}
javaBean
public class BizPatient {
private static final long serialVersionUID = 1L;
/**
* $column.columnComment
*/
//非空判斷
@NotNull(groups = {SaveGroup.class, SelectGroup.class}, message = "patientId 不能為空")
private Long patientId
}
如何校驗(yàn)關(guān)聯(lián)對(duì)象?
@PostMapping(value = "/saveOrUpdate")
public GbmResult saveOrUpdate(@RequestBody @Validated GdVo gdVo) {
}
@Data
public class GdVo {
@Valid
private GdAfterSalesDto gdAfterSalesDto;
@Valid
private List<GdProcessRecordDto> gdProcessRecordDto;
}
手動(dòng)校驗(yàn)工具類
- 有時(shí)候注解不生效贿堰,我們可以手動(dòng)校驗(yàn)
- 或者一個(gè)接口同時(shí)做add和update辙芍。如addAndUpdate接口。這個(gè)時(shí)候我們不好使用分組校驗(yàn)羹与。只能手動(dòng)校驗(yàn)
import org.springframework.validation.BindingResult;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
import java.util.stream.Collectors;
public class ValidParameterUtils {
private static Validator validator;
static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public static void validParameter(BindingResult validResult){
if (validResult.hasErrors()){
throw new GBMException(validResult.getFieldError().getDefaultMessage(),GbmResultCode.PARAMETER_EXCEPTION.code());
}
}
public static void validateEntity(Object object, Class<?>... groups)
throws GBMException {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
StringBuilder msg = new StringBuilder();
for(ConstraintViolation<Object> constraint: constraintViolations){
msg.append(constraint.getMessage()).append(" ");
}
throw new GBMException(msg.toString(),GbmResultCode.FAIL.code());
}
}
/**
* @Des 返回錯(cuò)誤信息
* @Author yinkai
* @Date 2022/2/28 9:24
*/
public static String validateEntityRString(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
return constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(" "));
}
}
使用
public class ZskQuestionsAndAnswersVo {
@NotNull(groups = {AddGroup.class}, message = "knowledgeId為空")
private Long knowledgeId;
@Length(max = 100, min = 1, message = "問(wèn)題必須在1-100字符之間")
@NotBlank(groups = {AddGroup.class}, message = "problemContent為空")
private String problemContent;
@Length(max = 500, min = 1, message = "回答必須在1-500字符之間")
@NotBlank(groups = {AddGroup.class}, message = "answer為空")
private String answer;
}
@PostMapping(value = "/addQuestionsAndAnswers")
public GbmResult addQuestionsAndAnswers(@RequestParam("img") MultipartFile[] img,
@RequestParam("vedio") MultipartFile[] vedio,
@Valid ZskQuestionsAndAnswersVo zskQuestionsAndAnswersVo) {
ValidParameterUtils.validateEntity(zskQuestionsAndAnswersVo,AddGroup.class);
}
還需定義全局異常處理器
@RestControllerAdvice
@Order(100)
public class GBMExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//處理Get請(qǐng)求中 使用@Valid 驗(yàn)證路徑中請(qǐng)求實(shí)體校驗(yàn)失敗后拋出的異常
@ExceptionHandler(org.springframework.validation.BindException.class)
@ResponseBody
public GbmResult BindExceptionHandler(BindingResult e) {
String message = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
return GbmResult.error(GbmResultCode.PARAMETER_EXCEPTION.getCode(),message);
}
校驗(yàn)List
Controller類上加@Validated
@Validated
public class ZskKnowledgeController {
handel方法上加
@PostMapping(value = "/saveOrUpdateZskAccessories")
public GbmResult saveOrUpdateZskAccessories(@RequestBody @Valid List<ZskAccessoriesListType> zskKnowledgeVoList) {
注解含義
@Pattern(regexp = "1[3|4|5|7|8][0-9]\d{8}",message = "手機(jī)號(hào)碼格式不正確")
@NotEmpty(message ="returnAndExchangeInformation 不能為空")
@NotNull(message ="knowledgeId 不能為空")
@Digits(integer = 10, fraction = 2, message = "補(bǔ)發(fā)運(yùn)費(fèi)格式錯(cuò)誤")
@Length(max = 50, min = 1, message = "配件名稱必須在1-50字符之間")
Constraint 詳細(xì)信息
@AssertFalse 該值必須為False
@AssertTrue 該值必須為True
@DecimalMax(value故硅,inclusive) 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 纵搁,inclusive表示是否包含該值
@DecimalMin(value吃衅,inclusive) 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 腾誉,inclusive表示是否包含該值
@Digits 限制必須為一個(gè)小數(shù)徘层,且整數(shù)部分的位數(shù)不能超過(guò)integer峻呕,小數(shù)部分的位數(shù)不能超過(guò)fraction
@Email 該值必須為郵箱格式
@Future 被注釋的元素必須是一個(gè)將來(lái)的日期
@FutureOrPresent 被注釋的元素必須是一個(gè)現(xiàn)在或?qū)?lái)的日期
@Max(value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
@Min(value) 被注釋的元素必須是一個(gè)數(shù)字趣效,其值必須大于等于指定的最小值
@Negative 該值必須小于0
@NegativeOrZero 該值必須小于等于0
@NotBlank 該值不為空字符串瘦癌,例如“ ”
@NotEmpty 該值不為空字符串
@NotNull 該值不為Null
@Null 該值必須為Null
@Past 被注釋的元素必須是一個(gè)過(guò)去的日期
@PastOrPresent 被注釋的元素必須是一個(gè)過(guò)去或現(xiàn)在的日期
@Pattern(regexp) 匹配正則
@Positive 該值必須大于0
@PositiveOrZero 該值必須大于等于0
@Size(min,max) 數(shù)組大小必須在[min,max]這個(gè)區(qū)間
自定義注解
手動(dòng)實(shí)現(xiàn)一個(gè)自定義注解,做到靈活指定字符串字段只包含數(shù)字跷敬、字母讯私、特殊符號(hào)、中文的校驗(yàn)
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {ContainCharValidator.class}
)
public @interface ContainChar {
String message() default "";
Class<?>[] groups() default {};
//必須包含這個(gè)干花,否則報(bào)錯(cuò)
//javax.validation.ConstraintDefinitionException: HV000074: com.gbm.cloud.treasure.entity.zsk.ContainChar contains Constraint annotation, but does not contain a payload parameter.
Class<? extends Payload>[] payload() default {};
ContainCharEnum[] value() default {ContainCharEnum.CHINESE, ContainCharEnum.NUMBER, ContainCharEnum.LETTER, ContainCharEnum.SYMBOL};
}
/**
* @Des
* @Author yinkai
* @Date 2022/3/1 14:38
*/
public class ContainCharValidator implements ConstraintValidator<ContainChar, String> {
private String message;
private ContainCharEnum[] values;
private Class<?>[] groups;
@Override
public void initialize(ContainChar constraintAnnotation) {
this.message = constraintAnnotation.message();
this.values = constraintAnnotation.value();
this.groups = constraintAnnotation.groups();
}
/**
* @Des 遍歷妄帘,全都不包含才返回false
* @Author yinkai
* @Date 2022/3/1 13:49
*/
public boolean isValid2(String value, ConstraintValidatorContext context) {
for (ContainCharEnum containCharEnum : values) {
switch (containCharEnum) {
case CHINESE:
if (!CHINESE.getPattern().matcher(value).find()) {
//禁止默認(rèn)消息返回
context.disableDefaultConstraintViolation();
//自定義返回消息
context.buildConstraintViolationWithTemplate(message+"不包含"+containCharEnum).addConstraintViolation();
return false;
}
break;
case NUMBER:
if (!NUMBER.getPattern().matcher(value).find()) {
//禁止默認(rèn)消息返回
context.disableDefaultConstraintViolation();
//自定義返回消息
context.buildConstraintViolationWithTemplate(message+"不包含"+containCharEnum).addConstraintViolation();
return false;
}
break;
case SYMBOL:
if (!SYMBOL.getPattern().matcher(value).find()) {
//禁止默認(rèn)消息返回
context.disableDefaultConstraintViolation();
//自定義返回消息
context.buildConstraintViolationWithTemplate(message+"不包含"+containCharEnum).addConstraintViolation();
return false;
}
break;
case LETTER:
if (!LETTER.getPattern().matcher(value).find()) {
//禁止默認(rèn)消息返回
context.disableDefaultConstraintViolation();
//自定義返回消息
context.buildConstraintViolationWithTemplate(message+"不包含"+containCharEnum).addConstraintViolation();
return false;
}
break;
default:
break;
}
}
return true;
}
//遍歷,全都不包含才返回false || 包含之外的就返回false
// !(包含一個(gè) && 只包含內(nèi)部)
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
HashSet<Boolean> booleans = new HashSet<>(2);
StringBuilder stringBuilder = new StringBuilder();
for (ContainCharEnum containCharEnum : values) {
booleans.add(containCharEnum.getPattern().matcher(value).find());
stringBuilder.append(containCharEnum);
}
//不包含true-->全都是false-->全都不包含
if (!booleans.contains(Boolean.TRUE)) {
//禁止默認(rèn)消息返回
context.disableDefaultConstraintViolation();
//自定義返回消息
context.buildConstraintViolationWithTemplate(message + value + "不包含 " + stringBuilder).addConstraintViolation();
return false;
}
Set<ContainCharEnum> noFindSet = Arrays.stream(values()).filter(m -> !ArrayUtil.contains(values, m)).collect(Collectors.toSet());
for (ContainCharEnum containCharEnum : noFindSet) {
if (containCharEnum.getPattern().matcher(value).find()) {
//禁止默認(rèn)消息返回
context.disableDefaultConstraintViolation();
//自定義返回消息
context.buildConstraintViolationWithTemplate(message + value + "不能包含 " + containCharEnum).addConstraintViolation();
return false;
}
}
return true;
}
}
public enum ContainCharEnum {
CHINESE(0, "中文",Pattern.compile("[\u4E00-\u9FA5|\\池凄!|\\抡驼,|\\。|\\(|\\)|\\《|\\》|\\“|\\”|\\肿仑?|\\:|\\致盟;|\\【|\\】]")),
NUMBER(1, "數(shù)字", Pattern.compile("[0-9]")),
LETTER(2, "字母",Pattern.compile(".*[a-zA-Z]+.*")),
SYMBOL(3, "特殊符號(hào)",Pattern.compile(".*[`~!@#$%^&*()+=|{}':;',\\[\\]·.<>/?~!@#¥%……&*()——+|{}【】‘尤慰;:”“’馏锡。,伟端、杯道?\\\\]+.*"));
@EnumValue//標(biāo)記數(shù)據(jù)庫(kù)存的值是code
private Integer code;
@JsonValue
private String desc;
private Pattern pattern;
ContainCharEnum(Integer code, String desc,Pattern pattern) {
this.code = code;
this.desc = desc;
this.pattern = pattern;
}
@Override
public String toString() {
return desc;
}
public int getValue() {
return code;
}
public Pattern getPattern() {
return pattern;
}
}