1. 使用场景
例如:当属性condition=1时,某某参数为必填;某某参数为必填且只能为某些值或不能为某些值。
支持扩展场景:
- 只允许填入某些值
- 不允许填入某些值
- 如果使用注解的属性是对象,可以控制是否对对象中的属性进行再校验
2. 技术实现
2.1 实现思路
使用Hibernate Validator
校验工具,自定义校验注解及其校验逻辑。
Hibernate Validator
官方文档:
https://docs.jboss.org/hibernate/validator/7.0/reference/en-US/html_single/#validator-gettingstarted
2.2 代码实现
2.2.1 引入依赖
<!--springboot2.3.3版本后 参数校验需加上 begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--springboot2.3.3版本后 参数校验需加上 end-->
2.2.2 自定义校验注解
/**
* 其他属性影响该注解属性校验
* 例如:当属性condition=1时被@OtherAffect注解的属性才做校验
* column字段不能欸空
*
* @author Gangbb
* @date 2021/9/19
**/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(OtherAffect.List.class)
@Constraint(validatedBy = {OtherAffectValidator.class})
public @interface OtherAffect {
/**
* 决定该字段是否需要校验的字段名
*/
String column();
/**
* column的会触发该校验的条件值
**/
String[] columnValue();
/**
* 条件触发时,只允许填的值(被注解属性不为对象)
*/
String[] allowedValues() default { };
/**
* 条件触发时,不允许填的值(被注解属性不为对象)
*/
String[] notAllowedValues() default { };
/**
* 自定义错误信息
*/
String message() default "";
/**
* 是否校验属性,开启则对其校验,不开启只判空。(一般需要校验对象或是对象列表时使用,默认关闭)
*/
boolean isCheck() default false;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface List {
OtherAffect[] value();
}
}
2.2.3 校验工具类
/**
* hibernate Validator校验工具类
*
* @author Gangbb
* @date 2022/1/29
**/
public class ValidatorUtils {
/**
* 验证器工程
*/
private static ValidatorFactory factory;
/**
* 对象验证器
*/
public static Validator validator;
/**
* 方法验证器
*/
public static ExecutableValidator executableValidator;
static {
initValidator();
clean();
}
/**
* @Author Gangbb
* @Description 初始化ValidatorFactory和Validator
* @Date 2021/9/21
**/
public static void initValidator() {
factory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
validator = factory.getValidator();
}
/**
* @Author Gangbb
* @Description 初始化ValidatorFactory和ExecutableValidator
* @Date 2021/9/21
**/
public static void initExecutableValidator() {
factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
}
/**
* @Author Gangbb
* @Description 关闭ValidatorFactory工厂
* @Date 2021/9/21
**/
public static void clean() {
factory.close();
}
/**
* @Author Gangbb
* @Description 对类中的某方法参数校验
* @Date 2021/9/21
**/
public static<T> Set<ConstraintViolation<T>> validMethod(T t, Method method, Object[] parameterValues){
return executableValidator.validateParameters(t, method, parameterValues);
}
/**
* 校验对象
*
* @Param [object:待检验对象, groups:对象分组]
* @return void
* @Author Gangbb
* @Date 2021/10/25
**/
public static void validateObject(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
if(CollectionUtil.isNotEmpty(constraintViolations)){
String errorMsg = StrUtil.format("对象{}校验异常:{}", object.getClass().getSimpleName(), getErrorMsg(constraintViolations));
throw new ApiException(ResultEnum.PARAMETER_VERIFICATION_FAIL.getCode(), errorMsg);
}
}
/**
* 校验对象列表
*
* @param objectList 待校验对象列表
* @param groups 校验分组
* @date 2022/1/8
**/
public static void validateObjectList(List<Object> objectList, Class<?>... groups) {
for (Object o : objectList) {
validateObject(o, groups);
}
}
/**
* 校验填入值是否合法
* 目前用于:DictValidator、EnumValidator
*
* @param allowedValues 允许填入值数组(在codeValues再)
* @param notAllowedValues 不允许填入值数组
* @param value 当前填入值须校验的值
* @return String
* @author Gangbb
* @date 2022/01/29
**/
public static String validateValues(String[] allowedValues, String[] notAllowedValues, Object value) {
// notAllowedValues存在情况
if (notAllowedValues != null && notAllowedValues.length > 0) {
List<String> notAllowedList = Arrays.asList(notAllowedValues);
if (notAllowedList.contains(String.valueOf(value))) {
return StrUtil.format("不能填写以下值{}", notAllowedList);
}
}
// allowedValues存在情况
if (allowedValues != null && allowedValues.length > 0) {
List<String> allowedList = Arrays.asList(allowedValues);
if (!allowedList.contains(String.valueOf(value))) {
return StrUtil.format("可填值只能为{}", Arrays.toString(allowedValues));
}
}
return "";
}
/**
* 校验填入值是否合法
* 目前用于:DictValidator、EnumValidator
*
* @param allowedValues 允许填入值数组(在codeValues再)
* @param notAllowedValues 不允许填入值数组
* @param value 当前填入值须校验的值
* @param codeValues 默认可填值
* @return String
* @author Gangbb
* @date 2021/11/18
**/
public static String validateValues(String[] allowedValues, String[] notAllowedValues,
Object value, List<Object> codeValues) {
// notAllowedValues存在情况
if(notAllowedValues != null && notAllowedValues.length > 0){
List<String> notAllowedList = Arrays.asList(notAllowedValues);
codeValues.removeAll(notAllowedList);
if(notAllowedList.contains(String.valueOf(value))){
return StrUtil.format("不能填写以下值{}", notAllowedList);
}
}
// allowedValues存在情况
if(allowedValues != null && allowedValues.length > 0){
List<String> allowedList = Arrays.asList(allowedValues);
// 将codeValues统一转成String
List<String> stringCodeValues = codeValues.stream().map(String::valueOf).collect(Collectors.toList());
if(!stringCodeValues.containsAll(allowedList)){
// @VerifyEnum填入allowedValues存在非枚举code值
throw new RuntimeException("填入allowedValues存在非允许值");
}else{
if(allowedList.contains(String.valueOf(value))){
return "";
}else{
return StrUtil.format("可填值只能为{}", Arrays.toString(allowedValues));
}
}
}
// 校验字段值是否是字典允许数据
if(codeValues.contains(value)){
return "";
}else{
// 重置错误提示
return StrUtil.format("可填值只能为{}", codeValues);
}
}
/**
* 获取校验错误消息
* @param c
* @return
*/
public static String getErrorMsg(Set<ConstraintViolation<Object>> c){
StringBuffer msg = new StringBuffer();
if (CollectionUtil.isNotEmpty(c)){
for (ConstraintViolation<Object> constraintViolation : c) {
String itemMessage = constraintViolation.getMessage();
String itemName = constraintViolation.getPropertyPath().toString();
msg.append("字段<").append(itemName).append(">--").append(itemMessage).append("。");
}
}
return msg.toString();
}
/**
* 拼装单个对象校验信息
*
* @Param [s, c]
* @return void
* @Author Gangbb
* @Date 2021/10/25
**/
public static void getOneInfo(StringBuffer s, Set<ConstraintViolation<Object>> c){
if (CollectionUtil.isNotEmpty(c)){
s.append("{ ");
for (ConstraintViolation<Object> constraintViolation : c) {
String itemMessage = constraintViolation.getMessage();
String itemName = constraintViolation.getPropertyPath().toString();
s.append("字段<" + itemName + "> :" + itemMessage + "。");
}
s.append(" }");
}
}
/**
* 拼装多个对象校验信息
*
* @Param [s, collect]
* @return void
* @Author Gangbb
* @Date 2021/10/25
**/
public static void getListInfo(StringBuffer s, List<Set<ConstraintViolation<Object>>> collect){
for (int i = 0; i < collect.size(); i++) {
Set<ConstraintViolation<Object>> constraintViolations = collect.get(i);
if (CollectionUtil.isNotEmpty(constraintViolations)){
s.append("[ " + "列表第["+ i + "]项校验不通过:");
getOneInfo(s, constraintViolations);
s.append(" ]");
}
}
}
/**
* 注解校验,获取处理校验结果
*
* @param errorMsg 错误信息
* @param context 校验上下文
* @return boolean
* @author Gangbb
* @date 2021/10/29
**/
public static boolean getResult(String errorMsg, ConstraintValidatorContext context){
if(StrUtil.isNotBlank(errorMsg)){
// 重置错误提示
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errorMsg)
.addConstraintViolation();
return false;
}
return true;
}
}
2.2.4 校验处理逻辑
public class OtherAffectValidator implements ConstraintValidator<OtherAffect, Object> {
private String[] columnValue;
private String column;
private boolean isCheck;
private String[] allowedValues;
private String[] notAllowedValues;
private static Validator validator;
/**
* 初始化数据
*
* @param constraintAnnotation OtherAffect注解实例
* @return void
* @author Gangbb
* @date 2021/9/20
**/
@Override
public void initialize(OtherAffect constraintAnnotation) {
columnValue = constraintAnnotation.columnValue();
column = constraintAnnotation.column();
isCheck = constraintAnnotation.isCheck();
allowedValues = constraintAnnotation.allowedValues();
notAllowedValues = constraintAnnotation.notAllowedValues();
// 获取校验Validator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
factory.close();
}
/**
* 校验逻辑方法
*
* @param o 当前字段的值
* @param context ConstraintValidatorContext校验上下文
* @return boolean
* @author Gangbb
* @date 2021/9/20
**/
@Override
public boolean isValid(Object o, ConstraintValidatorContext context) {
// 获取column的请求值,为空的话校验失败
String columnRequestValue = getColumnRequestValue();
if (StrUtil.isBlank(columnRequestValue)) {
return ValidatorUtils.getResult(StrUtil.format("请求值{}不能为空", column), context);
}
// 如果column的值存在于columnValue中
if (Arrays.asList(columnValue).contains(columnRequestValue)) {
// 被注解字段的值为空直接校验不通过
if (ObjectUtil.isEmpty(o)) {
return false;
}
// notAllowedValues、allowedValues存在情况
boolean b = (notAllowedValues != null && notAllowedValues.length > 0) || (allowedValues != null && allowedValues.length > 0);
if (b) {
String validResult = ValidatorUtils.validateValues(allowedValues, notAllowedValues, o);
return ValidatorUtils.getResult(validResult, context);
}
// 如果开启校验
if (isCheck) {
return validObject(o, context);
}
}
return true;
}
/**
* 获取column的请求值
*
* @return String
* @date 2022/1/13
**/
private String getColumnRequestValue(){
String paramValue = ServletUtils.getParameter(column);
// 如果从param获取不到,再找body
if(StringUtils.isBlank(paramValue)){
HttpServletRequest request = ServletUtils.getRequest();
// 获取column请求参数值
String body = ServletUtil.getBody(request);
if(StrUtil.isNotBlank(body)){
JSONObject jsonObject = JSONUtil.parseObj(body);
paramValue = String.valueOf(jsonObject.get(column));
}
}
return paramValue;
}
/**
* 校验传入对象
*
* @param o 当前字段的值
* @param context ConstraintValidatorContext校验上下文
* @return boolean
* @author Gangbb
* @date 2021/9/20
**/
private boolean validObject(Object o, ConstraintValidatorContext context){
// 定义错误信息
StringBuffer errorMsg = new StringBuffer();
boolean result = true;
// 当被注解属性为列表时
if(o instanceof ArrayList<?>){
List<Set<ConstraintViolation<Object>>> collect = new ArrayList<>();
for (Object oItem : (List<?>) o){
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(oItem);
collect.add(constraintViolations);
}
result = getListResult(collect);
ValidatorUtils.getListInfo(errorMsg, collect);
}else {
// 当被注解属性为单个对象时
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(o);
result = constraintViolations.isEmpty();
ValidatorUtils.getOneInfo(errorMsg, constraintViolations);
}
// 把自定义返回错误信息写入上下文
if(!result){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errorMsg.toString())
.addConstraintViolation();
}
return result;
}
/**
* 获取列表属性值校验结果
*
* @param collect 校验结果集合
* @return boolean
* @author Gangbb
* @date 2021/9/20
**/
private boolean getListResult(List<Set<ConstraintViolation<Object>>> collect){
boolean result = true;
for (Set<ConstraintViolation<Object>> constraintViolations : collect) {
result = constraintViolations.isEmpty();
}
return result;
}
3. 使用示例
@Data
public class SysDeptDto {
/**
* 组织架构d
*/
@NotNull(message = "组织架构Id不能为空", groups = { EditGroup.class })
private Long deptId;
/**
* 组织架构类别(字典类别:sys_dept_type)
*/
@NotNull(message = "组织架构类别不能为空", groups = { AddGroup.class, EditGroup.class })
@VerifyEnum(enumClass = SysDeptTypeEnum.class, keyColumn = "code", groups = { AddGroup.class, EditGroup.class })
private Integer type;
/**
* 父id
*/
@OtherAffect(column = "type", columnValue = {"2", "3", "4"}, message = "父id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long parentId;
}
解释:
当传入的type值为2、3、4时,这个父id传入值不能为空
其他扩展用法,有兴趣可以复制代码尝试!