wordpress會(huì)員插件上海搜索引擎優(yōu)化1
屬性校驗(yàn)
- 前言
- @Validated基礎(chǔ)用法
- 集合校驗(yàn)
- 分組校驗(yàn)
- 嵌套校驗(yàn)
- 自定義校驗(yàn)器
源碼地址
前言
在項(xiàng)目開(kāi)發(fā)過(guò)程中,經(jīng)常遇到需要對(duì)傳遞的參數(shù)進(jìn)行校驗(yàn),比如某個(gè)參數(shù)字段是否為空、值的取值是否在約定范圍、格式是否合法等等,最原始的寫(xiě)法,通過(guò)if判斷
@PostMappingpublic Result<Long> save(@RequestBody SystemUserDto systemUserDto) {if(systemUserDto.getUserName!=null){throw new BusinessException("用戶(hù)名稱(chēng)不能為空");}if(systemUserDto.getMobile!=null){throw new BusinessException("用戶(hù)電話不能為空");}if(systemUserDto.getMobile().length()>11){throw new BusinessException("用戶(hù)電話長(zhǎng)度不能超過(guò)11位");}return Result.ok(id);}
還有通過(guò)Spring框架提供的Assert
類(lèi),它能夠幫助我們確保方法參數(shù)符合預(yù)期,如果不符合會(huì)拋出IllegalArgumentException
,然后通過(guò)全局異常捕獲將錯(cuò)誤按照統(tǒng)一格式返給前端。
示例:校驗(yàn)字符串非空
import org.springframework.util.Assert;public void checkString(String input) {// 校驗(yàn)字符串非空,如果為空則拋出異常Assert.hasText(input, "輸入字符串不能為空");
}
常用的一些方法
Assert.notNull(Object object,"object is required"); // 對(duì)象非空
Assert.isTrue(Object object,"object must be true"); // 對(duì)象必須為true
Assert.notEmpty(Collection collection,"collection must not be empty"); // 集合不能為空
Assert.hasLength(String text,"text must be specified"); // 字符不為null且字符長(zhǎng)度不為0
Assert.hasText(String text,"text must not be empty"); // text不為null且必須至少包含一個(gè)非空的字符
Assert.isInstanceOf(Class class, Object object,"class must be of type[class]"); // object必須為class指定的類(lèi)
最后一種就是使用@Valid
注解和 @Validated
注解,首先我們要了解這兩個(gè)注解的區(qū)別,然后著重描述一下@Validated
用法
1.來(lái)源不同
@Validated
:是Spring框架特有的注解,屬于Spring的一部分,也是JSR 303的一個(gè)變種。它提供了一些 @Valid
所沒(méi)有的額外功能,比如分組驗(yàn)證。@Valid
:Java EE提供的標(biāo)準(zhǔn)注解,它是JSR
303規(guī)范的一部分,主要用于Hibernate Validation等場(chǎng)景。
2.注解位置
@Validated
: 用在類(lèi)、方法和方法參數(shù)上,但不能用于成員屬性。@Valid
:可以用在方法、構(gòu)造函數(shù)、方法參數(shù)和成員屬性上。
3.是否支持分組
@Validated
:支持分組驗(yàn)證,可以更細(xì)致地控制驗(yàn)證過(guò)程。此外,由于它是Spring專(zhuān)有的,因此可以更好地與Spring的其他功能(如Spring的依賴(lài)注入)集成。@Valid
:主要支持標(biāo)準(zhǔn)的Bean驗(yàn)證功能,不支持分組驗(yàn)證。
4.嵌套驗(yàn)證
@Validated
:不支持嵌套驗(yàn)證。@Valid
:支持嵌套驗(yàn)證,可以嵌套驗(yàn)證對(duì)象內(nèi)部的屬性。
@Validated基礎(chǔ)用法
① 在根目錄build.gradle
引入校驗(yàn)的依賴(lài)包
springValidationVersion = '3.3.2'
//Validation參數(shù)校驗(yàn)
implementation "org.springframework.boot:spring-boot-starter-validation:${springValidationVersion}"
② 在對(duì)象需要校驗(yàn)屬性上添加注解,比如我要驗(yàn)證賬號(hào)不能為空
@NotBlank(message = "用戶(hù)賬號(hào)不能為空")
private String username;
其它類(lèi)型的注解
注解 | 驗(yàn)證的數(shù)據(jù)類(lèi)型 | 描述 |
---|---|---|
@NotNull | 任意類(lèi)型 | 驗(yàn)證屬性不能為null |
@NotBlank | 字符串 | 驗(yàn)證字符串屬性不能為空且長(zhǎng)度必須大于0 |
@Size(min,max ) | CharSequence Collection Map Array | 字符串:字符串長(zhǎng)度必須在指定的范圍內(nèi) Collection:集合大小必須在指定的范圍內(nèi) Map:map的大小必須在指定的范圍內(nèi) Array:數(shù)組長(zhǎng)度必須在指定的范圍內(nèi) |
@Min | 整型類(lèi)型 | 驗(yàn)證數(shù)字屬性的最小值 |
@Max | 整型類(lèi)型 | 驗(yàn)證數(shù)字屬性的最大值 |
@DecimalMin | 數(shù)字類(lèi)型 | 驗(yàn)證數(shù)字屬性的最小值(包括小數(shù)) |
@DecimalMax | 數(shù)字類(lèi)型 | 驗(yàn)證數(shù)字屬性的最大值(包括小數(shù)) |
@Digits(integer,fraction) | 數(shù)字類(lèi)型 驗(yàn)證數(shù)字屬性的整數(shù)位數(shù)和小數(shù)位數(shù) | |
字符串類(lèi)型 | 驗(yàn)證字符串屬性是否符合Email格式 | |
@Pattern | 字符串 | 驗(yàn)證字符串屬性是否符合指定的正則表達(dá)式 |
@Positive | 數(shù)字類(lèi)型 | 驗(yàn)證數(shù)值為正數(shù) |
@PositiveOrZero | 數(shù)字類(lèi)型 | 驗(yàn)證數(shù)值為正數(shù)或0 |
@Negative | 數(shù)字類(lèi)型 | 驗(yàn)證數(shù)值為負(fù)數(shù) |
@NegativeOrZero | 數(shù)字類(lèi)型 | 驗(yàn)證數(shù)值為負(fù)數(shù)或0 |
@AssertTrue | 布爾類(lèi)型 | 參數(shù)值必須為 true |
@AssertFalse | 布爾類(lèi)型 | 參數(shù)值必須為 false |
@Past | 時(shí)間類(lèi)型(Date) | 參數(shù)值為時(shí)間,且必須小于 當(dāng)前時(shí)間 |
@PastOrPresent | 時(shí)間類(lèi)型(Date) | 參數(shù)值為時(shí)間,且必須小于或等于 當(dāng)前時(shí)間 |
@Future | 時(shí)間類(lèi)型(Date) | 參數(shù)值為時(shí)間,且必須大于 當(dāng)前時(shí)間 |
@FutureOrPresent | 時(shí)間類(lèi)型(Date) | 參數(shù)值為時(shí)間,且必須大于或等于 當(dāng)前日期 |
③ 在接口校驗(yàn)的對(duì)象前面添加上 @Validated
注解
@PostMapping
public Result<Long> save(@RequestBody @Validated SystemUserDto systemUserDto) {Long id=systemUserService.createUser(systemUserDto);return Result.ok(id);
}@PutMapping
public Result<Boolean> update(@RequestBody @Validated SystemUserDto systemUserDto) {systemUserService.updateUser(systemUserDto);return Result.ok(true);
}
④通過(guò)Apifox測(cè)試,可以看出返回的值并不能看出問(wèn)題,因此下一步結(jié)構(gòu)化一下異常顯示
后端報(bào)錯(cuò)
Resolved [org.springframework.web.bind.MethodArgumentNotValidException:Validation failed for argument [0] in public com.tps.cloud.response.Result<java.lang.Long>com.tps.cloud.system.controller.SystemUserController.save(com.tps.cloud.system.dto.SystemUserDto): [Field error in object 'systemUserDto' on field 'username': rejected value []; codes [NotBlank.systemUserDto.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [systemUserDto.username,username]; arguments []; default message [username]]; default message [用戶(hù)賬號(hào)不能為空]] ]
⑤ 給前端返回結(jié)構(gòu)化錯(cuò)誤提示,需要配置全局異常捕獲類(lèi)GlobalExceptionHandler
中添加捕獲MethodArgumentNotValidException
的方法
/*** validation Exception* @param exception* @return Result
*/
@ExceptionHandler({ MethodArgumentNotValidException.class })
public Result handleBodyValidException(MethodArgumentNotValidException exception) {FieldError fieldError = exception.getBindingResult().getFieldError();return Result.failed(String.format("%s", fieldError.getDefaultMessage()));
}
通過(guò)org.springframework.boot.autoconfigure.AutoConfiguration.imports
完成自動(dòng)配置注冊(cè)
⑥ 通過(guò)Apifox測(cè)試,可以看出返回的值已經(jīng)結(jié)構(gòu)化
集合校驗(yàn)
分組校驗(yàn)
分組驗(yàn)證是為了在不同的驗(yàn)證場(chǎng)景下能夠?qū)?duì)象的屬性進(jìn)行靈活地驗(yàn)證,從而提高驗(yàn)證的精細(xì)度和適用性。一般我們?cè)趯?duì)同一個(gè)對(duì)象進(jìn)行保存或修改時(shí),會(huì)使用同一個(gè)類(lèi)作為入?yún)?。那么在?chuàng)建時(shí)需要校驗(yàn)?zāi)硞€(gè)字段,但是更新的時(shí)候不需要校驗(yàn),這個(gè)時(shí)候就需要用到分組校驗(yàn)了。
對(duì)于定義分組有兩點(diǎn)要特別注意:
- 定義分組必須使用接口
- 要校驗(yàn)字段上必須加上分組,分組只對(duì)指定分組生效,不加分組不校驗(yàn)
① 創(chuàng)建分組
用于創(chuàng)建時(shí)指定分組:
package com.tps.cloud.group;public interface AddGroup {}
用于更新時(shí)指定分組:
package com.tps.cloud.group;public interface UpdateGroup {}
② 在實(shí)體類(lèi)上添加注解,我們只添加了AddGroup.class
分組
/**
* 用戶(hù)賬號(hào)
*/
@NotBlank(message = "用戶(hù)賬號(hào)不能為空",groups = {AddGroup.class})
private String username;
/**
* 密碼
*/
@NotBlank(message = "密碼不能為空",groups = {AddGroup.class})
private String password;
③ 在新增接口上添加分組,更新不添加分組,通過(guò)Apifox測(cè)試
@PostMapping
public Result<Long> save(@RequestBody @Validated({AddGroup.class}) SystemUserDto systemUserDto) {Long id=systemUserService.createUser(systemUserDto);return Result.ok(id);
}@PutMapping
public Result<Boolean> update(@RequestBody @Validated SystemUserDto systemUserDto) {systemUserService.updateUser(systemUserDto);return Result.ok(true);
}
④在實(shí)體類(lèi)以及對(duì)應(yīng)接口上添加UpdateGroup.class
分組,通過(guò)Apifox測(cè)試
/*** 用戶(hù)賬號(hào)*/@NotBlank(message = "用戶(hù)賬號(hào)不能為空",groups = {AddGroup.class,UpdateGroup.class})private String username;/*** 密碼*/@NotBlank(message = "密碼不能為空",groups = {AddGroup.class,UpdateGroup.class})private String password;
@PutMappingpublic Result<Boolean> update(@RequestBody @Validated({UpdateGroup.class}) SystemUserDto systemUserDto) {systemUserService.updateUser(systemUserDto);return Result.ok(true);}
嵌套校驗(yàn)
嵌套校驗(yàn)(Nested Validation) 指的是在驗(yàn)證對(duì)象時(shí),對(duì)對(duì)象內(nèi)部包含的其他對(duì)象進(jìn)行遞歸驗(yàn)證的過(guò)程。當(dāng)一個(gè)對(duì)象中包含另一個(gè)對(duì)象作為屬性,并且需要對(duì)這個(gè)被包含的對(duì)象也進(jìn)行驗(yàn)證時(shí),就需要進(jìn)行嵌套校驗(yàn)。
嵌套屬性指的是在一個(gè)對(duì)象中包含另一個(gè)對(duì)象作為其屬性的情況。換句話說(shuō),當(dāng)一個(gè)對(duì)象的屬性本身又是一個(gè)對(duì)象,那么這些被包含的對(duì)象就可以稱(chēng)為嵌套屬性。
我們繼續(xù)以保存用戶(hù)接口為例:
① 創(chuàng)建SystemDeptDto
類(lèi),并添加驗(yàn)證注解,現(xiàn)在把SystemDeptDto
作為SystemUserDto
的嵌套屬性
package com.tps.cloud.system.dto;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.tps.cloud.entity.TenantEntity;
import com.tps.cloud.group.AddGroup;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** 部門(mén)傳輸類(lèi)*/
@Data
public class SystemDeptDto{/*** 部門(mén)id*/@TableIdprivate Long id;/*** 部門(mén)名稱(chēng)*/@NotBlank(message = "部門(mén)名稱(chēng)不能為空",groups = {AddGroup.class})private String name;/*** 父部門(mén)id*/private Long parentId;/*** 顯示順序*/private Integer sort;/*** 負(fù)責(zé)人id*/private Long leaderUserId;/*** 聯(lián)系電話*/private String phone;/*** 郵箱*/private String email;/*** 部門(mén)狀態(tài)(0正常 1停用)*/@Validprivate Integer status;
}
public class SystemUserDto {...省略前面屬性/*** 部門(mén)*/private SystemDeptDto systemDept;
}
@PostMapping
public Result<Long> save(@RequestBody @Validated SystemUserDto systemUserDto) {Long id=systemUserService.createUser(systemUserDto);return Result.ok(id);
}
② 測(cè)試
自定義校驗(yàn)器
有時(shí),內(nèi)置的校驗(yàn)器無(wú)法滿(mǎn)足您的需求。例如,您可能需要驗(yàn)證用戶(hù)名是否唯一,這需要訪問(wèn)數(shù)據(jù)庫(kù)。在這種情況下,您可以定義自己的校驗(yàn)器。
要定義自定義校驗(yàn)器,請(qǐng)創(chuàng)建一個(gè)實(shí)現(xiàn) javax.validation.ConstraintValidator
接口的類(lèi)。在下面的示例中,我們將創(chuàng)建一個(gè)用于驗(yàn)證用戶(hù)名是否唯一的校驗(yàn)器:
① 創(chuàng)建校驗(yàn)器UniqueUsernameValidator
,ConstraintValidator
包含以下兩種方法:
- 初始化方法 initialize:這個(gè)方法在驗(yàn)證器的生命周期中僅被調(diào)用一次。它傳遞了與驗(yàn)證器關(guān)聯(lián)的注解實(shí)例,允許驗(yàn)證器從注解實(shí)例中提取和存儲(chǔ)配置詳情。
- 驗(yàn)證方法isValid: 這是實(shí)現(xiàn)驗(yàn)證邏輯的地方。這個(gè)方法對(duì)于每個(gè)要驗(yàn)證的值都會(huì)被調(diào)用,并返回一個(gè)布爾值,表示數(shù)據(jù)是否符合約束條件。
package com.tps.cloud.system.constraint;import com.tps.cloud.system.service.SystemUserService;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.AllArgsConstructor;@AllArgsConstructor
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {private final SystemUserService systemUserService;@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if (value == null) {return true;}return systemUserService.findByUsername(value) == null;}
}
② 創(chuàng)建一個(gè)自定義注解UniqueUsername
,以便在代碼中使用
package com.tps.cloud.system.constraint;import jakarta.validation.Constraint;
import jakarta.validation.Payload;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {UniqueUsernameValidator.class})
public @interface UniqueUsername {String message() default "用戶(hù)名已存在";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
② 在用戶(hù)賬號(hào)屬性上添加改注解
public class SystemUserDto {.../*** 用戶(hù)賬號(hào)*/@NotBlank(message = "用戶(hù)賬號(hào)不能為空",groups = {AddGroup.class,UpdateGroup.class})@UniqueUsernameprivate String username;...
}
③ 測(cè)試接口