Open GenweiWu opened 2 years ago
//这样不行
@Range(min = 1, max = 30, message = "timeout range invalid")
@Pattern(regexp = "^[1-9][0-9]*$",message = "timeout is invalid")
private int timeout=1 ;
No validator could be found for constraint 'javax.validation.constraints.Pattern' validating type 'java.lang.Integer'
//也不行
@Range(min = 1, max = 30, message = "timeout range invalid")
@Digits(integer = 2, fraction = 0)
private int timeout = 1;
org.hibernate.validator.internal.constraintvalidators.bv.DigitsValidatorForNumber 转换为int时,11.3这种会转换为11,所以校验通过 即11.3校验通过,但是读取的数据还是11
if ( num instanceof BigDecimal ) { bigNum = (BigDecimal) num; } else { bigNum = new BigDecimal( num.toString() ).stripTrailingZeros(); }
@Positive
是支持小数的,比如float、decimal都可以
可以参考:org.hibernate.validator.constraints.Range
import org.hibernate.validator.constraints.Length;
import javax.validation.Constraint;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.lang.annotation.*;
//这里有三个基本的校验组合在一起
@NotEmpty(message = "name is required")
@Length(max = 30, message = "name length is invalid")
@Pattern(regexp = "^[0-9a-zA-Z_\u4e00-\u9fa5]+$", message = "name character is invalid")
@Documented
@Constraint(validatedBy = {}) //这里的validatedBy为空就行
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@ReportAsSingleViolation
public @interface ValidateName {
@OverridesAttribute(constraint = Length.class, name = "max") int max() default 30; //还可以覆盖复合的校验注解的属性
String message() default "name is invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Documented
@Constraint(validatedBy = {})
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
//@ReportAsSingleViolation //如果这里设置了该注解,则报错时返回的是1;如果没设置,则返回具体错误即2处
@URL(regexp = REGEX_IP, message = "endpoint url is invalid") //2
@Length(max = 100, message = "endpoint length is invalid") //2
public @interface ValidateEndpoint {
@OverridesAttribute(constraint = Length.class, name = "max") int max() default 100;
String message() default "endpoint is invalid"; //1
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
枚举校验的注解
@Documented @Constraint(validatedBy = {EnumValueStringValidator.class, EnumValueIntegerValidator.class}) //validatedBy要写上所有对应的校验类 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @NotNull(message = "Value cannot be null") @ReportAsSingleViolation //写上这个注解,则报错信息只会显示下方的"Value is not valid" public @interface EnumValue {
Class<? extends java.lang.Enum<?>> enumClazz();
String message() default "Value is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
> 针对字符串和Integer要分别写校验类
```java
public class EnumValueIntegerValidator implements ConstraintValidator<EnumValue, Integer> {
private List<String> valueList = new ArrayList<>();
@Override
public void initialize(EnumValue constraintAnnotation) {
Class<? extends Enum<?>> enumClazz = constraintAnnotation.enumClazz();
Enum<?>[] enumConstants = enumClazz.getEnumConstants();
for (Enum<?> enumConstant : enumConstants) {
if (enumConstant instanceof EnumValidatable) {
EnumValidatable enumValidatable = (EnumValidatable) enumConstant;
valueList.add(enumValidatable.getValue());
} else {
valueList.add(enumConstant.name());
}
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
return valueList.contains(String.valueOf(value));
}
}
public class EnumValueStringValidator implements ConstraintValidator<EnumValue, String> {
private List<String> valueList = new ArrayList<>();
@Override
public void initialize(EnumValue constraintAnnotation) {
Class<? extends Enum<?>> enumClazz = constraintAnnotation.enumClazz();
Enum<?>[] enumConstants = enumClazz.getEnumConstants();
for (Enum<?> enumConstant : enumConstants) {
if (enumConstant instanceof EnumValidatable) {
EnumValidatable enumValidatable = (EnumValidatable) enumConstant;
valueList.add(enumValidatable.getValue());
} else {
valueList.add(enumConstant.name());
}
}
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return valueList.contains(value);
}
}
@Data
public class UserForm {
/**
* id
*/
@NotNull(message = "更新时id不能为空", groups = {Update.class})
private String id;
/**
* 类型
*/
@NotEmpty(message = "姓名不能为空" , groups = {Insert.class,Update.class})
private String name;
/**
* 年龄
*/
@NotEmpty(message = "年龄不能为空" , groups = {Insert.class,Update.class})
private String age;
}
public interface Insert {
}
public interface Update {
}
/**
* 添加用户
*/
@PostMapping("/addUser")
public ResultVo addUser(@RequestBody @Validated({Insert.class}) UserForm form){
// 选择对应的分组进行校验
return ResultVoUtil.success(form);
}
/**
@Data
public class UserForm {
/**
* id
*/
@NotNull(message = "更新时id不能为空", groups = {Update.class})
private String id;
/**
* 类型
*/
@NotEmpty(message = "姓名不能为空")
private String name;
/**
* 年龄
*/
@NotEmpty(message = "年龄不能为空")
private String age;
}
public interface Insert {
}
public interface Update {
}
import javax.validation.groups.Default;
/**
/**
@RestControllerAdvice
@Slf4j
public class ErrorCodeExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ErrorResponse invalidFormatHandler(MethodArgumentNotValidException e){
String message = e.getBindingResult().getFieldErrors().stream().
map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
return new ErrorResponse(SYSTEM_ERROR, message);
}
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ErrorResponse invalidConstraintHandler(ConstraintViolationException e){
List<String> msgList = new ArrayList<>();
for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()){
msgList.add(constraintViolation.getMessage());
}
String message = StringUtils.join(msgList.toArray(),",");
return new ErrorResponse(SYSTEM_ERROR, message);
}
}
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
@Autowired
private RateLimitService rateLimitService;
@Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& @annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
如果加了valid注解且校验失败,则上面的aop不会被触发
@RateLimited
@RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
@Valid @RequestBody CreateAccountRequestDto dto) {
...
}
使用拦截器代替aop
@Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
@Autowired
private final RateLimitService rateLimitService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
集合不能是null,或者为空集合 集合内容不能全是空,[" "]会报错,[" ","1"]有一个不为空则不会报错
class TestRequest{
@NotEmpty(message = "ids is empty")
private List<String> ids;
}
public void test(@RequestBody @Valid TestRequest testRequest)
list至少包含1个元素,最多包含15个元素
public class Mock {
@Size(min=1, max=3)
private List<String> strings;
public List<String> getStrings() {
return strings;
}
public void set(List<String> strings) {
this.strings = strings;
}
}
https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/validation.html
@Component
public class UserValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return User2.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");
User2 p = (User2) target;
if (p.getId() == 0) {
errors.rejectValue("id", "can not be zero");
}
}
}
@RestController
public class UserController {
@Autowired
UserValidator validator;
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
@RequestMapping(value = "/user/post", method = RequestMethod.POST)
public ServiceResponse handValidatePost(@Validated @RequestBody User user) {
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setCode(0);
serviceResponse.setMessage("test");
return serviceResponse;
}
}
https://stackoverflow.com/a/2783859
@AddressAnnotation public class Address { @NotNull @Max(50) private String street1; @Max(50) private String street2; @Max(10) @NotNull private String zipCode; @Max(20) @NotNull String city; @NotNull private Country country;
...
}
@Constraint(validatedBy = MultiCountryAddressValidator.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AddressAnnotation { String message() default ""; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
public class MultiCountryAddressValidator implements ConstraintValidator<AddressAnnotation, Address> { public void initialize(AddressAnnotation constraintAnnotation) { // initialize the zipcode/city/country correlation service }
/**
* Validate zipcode and city depending on the country
*/
public boolean isValid(Address object, ConstraintValidatorContext context) {
if (!(object instanceof Address)) {
throw new IllegalArgumentException("@AddressAnnotation only applies to Address objects");
}
Address address = (Address) object;
Country country = address.getCountry();
if (country.getISO2() == "FR") {
// check address.getZipCode() structure for France (5 numbers)
// check zipcode and city correlation (calling an external service?)
return isValid;
} else if (country.getISO2() == "GR") {
// check address.getZipCode() structure for Greece
// no zipcode / city correlation available at the moment
return isValid;
}
// ...
}
}
### 也可以自定义错误信息
> javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final-sources.jar!\javax\validation\ConstraintValidatorContext.java
```java
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
...
//disable existing violation message
context.disableDefaultConstraintViolation();
//build new violation message and add it
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
...
}
校验
RequestBody
参数校验
PathVariable
、RequestParam