如何優雅的做參數校驗-JSR330
前言:
本文不是講@Validate、@Valid是如何使用的、區別是什麼,想看這些內容的請換篇文章。
背景:
當前端傳過來的參數是進行對稱性加密、base64加密等處理後過的參數時,在controller介面使用@Validae、@Valid 主鍵是沒用的。
介面收到加密參數後,第一步應該是解密,然後再轉換為實際的參數。那麼判斷參數欄位是否符合條件又要寫程式碼去判斷了,不夠優雅。
那麼我是怎麼做的呢?看下文...
假設我們現在有一個登錄介面,大概是下面這樣↓↓↓↓↓↓↓↓↓↓↓
原本的登錄邏輯
@PostMapping(value = "/login")
public R login(@RequestParam(value = "param") String param){
// 1:將參數解密出來拿到解密後的字元串(對稱性解密)
String realParam = rsa.decryptStr(encryptParamsVO.getParams(), KeyType.PrivateKey, StandardCharsets.UTF_8);
// 2:將解密後的字元串轉換為dto
LoginDto dto = JSON.parseObject(realParam , UserDto.class);
// 3:靠程式碼進行欄位規則判斷
if(StringUtils.isBlank(dto.getUsername)){
throw new ServiceException("password不能為空");
}
if(StringUtils.isBlank(dto.getPassword)){
throw new ServiceException("password不能為空");
}
// 4:校驗通過後登陸
userService.login(dto);
return R.success();
}
可以看到第三步有這明顯的缺陷,如果 LoginDto 參數一多,那麼就要寫很多 if 語句程式碼來進行判斷,這樣勢必是不優雅的寫法。
參數類
@Data
public class LoginDto {
@NotBlank(message = "用戶名不能為空")
@Size(min = 6, max = 16, message = "用戶名格式不正確")
private String username;
@NotBlank(message = "密碼不能為空")
@Size(min = 8, max = 16, message = "密碼格式不正確")
private String password;
}
這個類其實才是介面中實際用到的參數類。而它本身的欄位其實是用了@NotNull、@Size註解修飾過的,只不過沒有起到作用。
那麼參數在解密後轉換為 LoginDto 後如何讓這些註解起到原本的作用呢?
自己寫個工具類
@Slf4j
public class JSRValidatorUtil {
private final static Validator VALIDATOR = Validation.byProvider(HibernateValidator.class)
.configure()
.buildValidatorFactory().getValidator();
public static <T> void validate(T param) {
Set<ConstraintViolation<T>> validate = VALIDATOR.validate(param);
validate.forEach(v -> {
log.error("JSR校驗異常,property:{},message:{}", v.getPropertyPath(), v.getMessage());
throw new ServiceException(v.getMessage());
});
}
}
看看測試效果:
@Test
public void validateTest() {
LoginDto loginDto = new LoginDto();
loginDto.setUsername("123456");
loginDto.setPassword("123456");
JSRValidatorUtil.validate(loginDto);
}
利用Spring自帶的Validator、SpringValidatorAdapter
@Resource
private SpringValidatorAdapter adapter;
@Resource
private Validator validator;
@Test
public void validate() {
LoginDto loginDto = new LoginDto();
loginDto.setUsername("123456");
loginDto.setPassword("123456");
Set<ConstraintViolation<LoginDto>> validate1 = adapter.validate(loginDto);
Set<ConstraintViolation<LoginDto>> validate2 = validator.validate(loginDto);
validate1.forEach(v -> logger.error("JSR校驗異常,property:{},message:{}", v.getPropertyPath(), v.getMessage()));
validate2.forEach(v -> logger.error("JSR校驗異常,property:{},message:{}", v.getPropertyPath(), v.getMessage()));
}
自定義符合JSR330規範的註解
很多場景下需要自定義註解實現的,比如欄位屬性為 性別、密碼、身份證 等等。
自定義一個密碼格式校驗註解:
@Constraint(validatedBy = PasswordValidation.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
String message() default "密碼必須包含大小寫英文字元、數字、特殊字元";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
規則實現類:
public class PasswordValidation implements ConstraintValidator<Password, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean haveDigit = false;
boolean uppercase = false;
boolean lowercase = false;
boolean special = false;
char v;
for (int i = 0; i < value.length(); i++) {
v = value.charAt(i);
if (Character.isDigit(v))
haveDigit = true;
else if (Character.isUpperCase(v))
uppercase = true;
else if (Character.isLowerCase(v))
lowercase = true;
}
if (Pattern.compile("[ _`~!@#$%^&*()+=|{}':;,\\[\\].<>/?!¥…()—【】『;:」「』。,、?]|\n|\r|\t").matcher(value).find())
special = true;
return haveDigit && uppercase && lowercase && special;
}
}
使用@Password 註解修飾
@Password
private String password;
看效果: