javax.validation和hibernate-validator參數(shù)校驗(yàn)

@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;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市责蝠,隨后出現(xiàn)的幾起案子党巾,更是在濱河造成了極大的恐慌,老刑警劉巖霜医,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齿拂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肴敛,警方通過(guò)查閱死者的電腦和手機(jī)署海,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)医男,“玉大人砸狞,你說(shuō)我怎么就攤上這事《扑螅” “怎么了趾代?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)丰辣。 經(jīng)常有香客問(wèn)我撒强,道長(zhǎng),這世上最難降的妖魔是什么笙什? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任飘哨,我火速辦了婚禮,結(jié)果婚禮上琐凭,老公的妹妹穿的比我還像新娘芽隆。我一直安慰自己,他們只是感情好统屈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布胚吁。 她就那樣靜靜地躺著,像睡著了一般愁憔。 火紅的嫁衣襯著肌膚如雪腕扶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天吨掌,我揣著相機(jī)與錄音半抱,去河邊找鬼。 笑死膜宋,一個(gè)胖子當(dāng)著我的面吹牛窿侈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秋茫,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼史简,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肛著?” 一聲冷哼從身側(cè)響起圆兵,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎策泣,沒(méi)想到半個(gè)月后衙傀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萨咕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年统抬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片危队。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聪建,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茫陆,到底是詐尸還是另有隱情金麸,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布簿盅,位于F島的核電站挥下,受9級(jí)特大地震影響揍魂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棚瘟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一现斋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偎蘸,春花似錦庄蹋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至章咧,卻和暖如春倦西,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慧邮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工调限, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人误澳。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓耻矮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親忆谓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裆装,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355