一、需求背景
对于一些业务场景,接口字段很多,且对字段格式有特殊要求、或者各个字段之间有级联关系,为了写出优雅的代码,避免接口字段校验逻辑对业务主体逻辑的侵入,考虑使用注解方式去校验字段,当然也支持自定义注解。
二、实现方案
使用Spring Validation注解实现对接口入参的校验。
接下来展示,使用自定义注解校验入参某几个字段之间的校验,例如入参的“省、市、区”的级联校验。
三、编码实现
3.1 项目结构
3.2 自定义注解
package com.saferycom.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* @author LH
*/
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PccMatchValidator.class)
public @interface PccMatch {
// 省
String prov();
// 市
String city();
// 区
String country();
// 默认错误消息
String message() default "Invalid value for MyConstraint";
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
3.3 自定义校验器
package com.saferycom.validate;
import org.springframework.beans.BeanWrapperImpl;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author LH
*/
public class PccMatchValidator implements ConstraintValidator<PccMatch, Object> {
private String prov;
private String city;
private String country;
@Override
public void initialize(PccMatch args) {
// 初始化逻辑
this.prov = args.prov();
this.city = args.city();
this.country = args.country();
}
@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
// 结构体为空的默认返回true
if (obj == null) {
return true;
}
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(obj);
String province = (String) beanWrapper.getPropertyValue(this.prov);
String city1 = (String) beanWrapper.getPropertyValue(this.city);
String country1 = (String) beanWrapper.getPropertyValue(this.country);
// todo 这里可以添加具体的验证逻辑,这里简单示例
if ("31000".equals(province) && "31100".equals(city1) && "31101".equals(country1)) {
return true;
}
return false;
}
}
3.4 定义一个接口的入参,对需要校验的字段打上自定义注解
package com.saferycom.validate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.Valid;
/**
* @author LH
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Valid
@PccMatch(prov = "prov", city = "city", country = "country", message = "省市区级联校验失败")
public class RegisterAddress {
private String id;
private String prov;
private String city;
private String country;
}
3.5 定义一个接口,进行测试
package com.saferycom.validate;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import java.util.Set;
/**
* @author LH
*/
@RestController
@RequestMapping("/test")
public class ValidateTestController {
@Resource
private LocalValidatorFactoryBean localValidatorFactoryBean;
@PostMapping("/validate")
public String validateTest(@RequestBody RegisterAddress address) {
Set<ConstraintViolation<RegisterAddress>> violations = localValidatorFactoryBean.getValidator().validate(address);
return "ok";
}
}
3.5 启动服务,使用ApiPost调用接口查看校验情况
断点测试,查看校验结果
如上图,我们拿到的校验结果。
四、总结
以上,就是使用自定义注解实现接口入参校验,后续,我们校验完成之后,可以考虑抛出异常,然后对抛出的异常进行统一收集处理,然后统一返回给接口调用者,这里的操作我们放到下期进行展示。
期待一下吧,一起学习,一起进步。