SpringBoot系列之自定义枚举类的数据校验注解

x33g5p2x  于2021-12-16 转载在 Spring  
字(6.6k)|赞(0)|评价(0)|浏览(508)

SpringBoot系列之自定义枚举类的数据校验注解
业务场景:数据校验,需要对枚举类型的数据传参,进行数据校验,不能随便传参。拓展,支持多个参数的枚举数据校验

在网上找到很多参考资料,所以本博客基于这些博客进行拓展补充,ok,先建一个springboot项目

项目环境:

  • JDK 1.8

  • SpringBoot2.2.1

  • Maven 3.2+

  • 开发工具

  • IntelliJ IDEA

  • smartGit
    创建一个SpringBoot Initialize项目

选择jdk8

选择lombok和spring web

项目建好之后,在maven配置文件加上:

<dependency>
     <groupId>javax.validation</groupId>
     <artifactId>validation-api</artifactId>
     <version>2.0.1.Final</version>
 </dependency>

这里可以先写个例子进行验证,写个枚举类,表示多种支付类型,比如支付宝执法,微信支付等等

package com.example.common.util.validator.sample.enums;

public enum PayTypeEnum {

    Cash("1","现金"),
    Alipay("2","支付宝"),
    WeChatPay("3","微信支付"),
    BankCard("4","银行卡支付"),
    CreditCard("5","信用卡支付");

    PayTypeEnum(String code , String desc) {
        this.code = code;
        this.desc = desc;
    }

    private String code;
    private String desc;

    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }

}

因为要校验传入参数是否为枚举类里的类型,可以在PayTypeEnum类里新增一个校验方法

public static boolean isValueValid(String value) {
    if(!StringUtils.isEmpty(value)){
        for (PayTypeEnum enumObj : PayTypeEnum.values()) {
            if (enumObj.getCode().equals(value)) {
                return true;
            }
        }
        return false;
    }
    return true;
}

这里是加一下自定义的元注解类,然后通过@Constraint指定具体的校验类,通过反射机制获取对应的方法,比如isValueValid这个方法

package com.example.common.util.validator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValueValidator.Validator.class)
public @interface EnumValueValidator {

    Logger log = LoggerFactory.getLogger(EnumValueValidator.class);

    String message() default "参数有误";

    Class<? extends Enum<?>> enumClass();

    String enumMethod();
    
    class Validator implements ConstraintValidator<EnumValueValidator , Object> {
        private Class<? extends Enum<?>> enumClass;
        private String enumMethod;

        @Override
        public void initialize(EnumValueValidator constraintAnnotation) {
            enumMethod = constraintAnnotation.enumMethod();
            enumClass = constraintAnnotation.enumClass();
        }
        @Override
        public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
          // 值没传的情况,直接返回true
            if (StringUtils.isEmpty(o)) return Boolean.TRUE;
            if (enumClass == null || StringUtils.isEmpty(enumMethod)) return Boolean.TRUE;
            Class<?> vclass = o.getClass();
            try {
            // 反射机制获取具体的校验方法
                Method method = enumClass.getMethod(enumMethod,vclass);
                if (!Boolean.TYPE.equals(method.getReturnType()) &&
                        !Boolean.class.equals(method.getReturnType())) {
                    throw new RuntimeException("校验方法不是布尔类型!");
                }
                if (!Modifier.isStatic(method.getModifiers())) {
                    throw new RuntimeException("校验方法不是静态方法!");
                }
                method.setAccessible(true);
                // 调用具体的方法
                Boolean res = (Boolean) method.invoke(null,o);
                return res != null ? res : false;
            } catch (NoSuchMethodException e) {
                log.error("NoSuchMethodException:{}" ,e);
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                log.error("IllegalAccessException:{}" ,e);
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                log.error("InvocationTargetException:{}" ,e);
                throw new RuntimeException(e);
            }
        }
    }

}

具体的bean类,加上@EnumValueValidator(enumClass = PayTypeEnum.class , enumMethod = "isStrsValid" , message = "支付类型校验有误")指向具体的枚举类和校验方法

package com.example.common.util.validator.sample.model;

import com.example.common.util.validator.EnumValueValidator;
import com.example.common.util.validator.sample.enums.PayTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

import javax.validation.constraints.NotNull;

@Data
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder(toBuilder = true)
@ToString
public class ShopOrder {

    @EnumValueValidator(enumClass = PayTypeEnum.class , enumMethod = "isStrsValid" , message = "支付类型校验有误")
    @NotNull(message = "支付类型必须传")
    private String payType;

}

加上@Validated开启校验

package com.example.common.util.validator.sample.controller;

import com.example.common.util.validator.sample.model.ShopOrder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api/orders")
public class SampleController {

    @PostMapping
    public String saveOrder(@Validated ShopOrder shopOrder) {
        return shopOrder.toString();
    }
}

校验出错返回:

{
    "timestamp": "2021-12-16T10:01:27.801+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "EnumValueValidator.shopOrder.payType",
                "EnumValueValidator.payType",
                "EnumValueValidator.java.lang.String",
                "EnumValueValidator"
            ],
            "arguments": [
                {
                    "codes": [
                        "shopOrder.payType",
                        "payType"
                    ],
                    "arguments": null,
                    "defaultMessage": "payType",
                    "code": "payType"
                },
                "com.example.common.util.validator.sample.enums.PayTypeEnum",
                {
                    "defaultMessage": "isStrsValid",
                    "arguments": null,
                    "codes": [
                        "isStrsValid"
                    ]
                }
            ],
            "defaultMessage": "支付类型校验有误",
            "objectName": "shopOrder",
            "field": "payType",
            "rejectedValue": "2,111",
            "bindingFailure": false,
            "code": "EnumValueValidator"
        }
    ],
    "message": "Validation failed for object='shopOrder'. Error count: 1",
    "path": "/api/orders"
}

拓展,这里要求传payType类型,也就是支持多选的情况,参数payType=2,111,这里要修改校验方法:

public static boolean isStrsValid(String value) {
    if (!value.contains(","))
        return isValueValid(value);
    String[] arr = StringUtils.split(value , ",");
    for (String s : arr) {
        if (!isValueValid(s)) {
            return false;
        }
    }
   return true;
}

加上注解,enumMethod 改成isStrsValid

@EnumValueValidator(enumClass = PayTypeEnum.class , enumMethod = "isStrsValid" , message = "支付类型校验有误")

调用这个接口:

http://127.0.0.1:8080/api/orders?payType=2,111

相关文章