文章目录
- 1. 应用场景
- 2. 回顾注解校验的使用
- 3. 自定义注解校验整型枚举值
- 4. 自定义注解校验各种类型枚举值
- 4.1 使用示例1
- 4.2 使用示例2
- 4.3 使用示例3
- 4.4 实现自定义注解
- 5. 自定义注解校验整型和字符串类型枚举值
1. 应用场景
在实际工作的项目中,我们经常会遇到参数校验的问题,比如status字段我们只允许前端传给我们0,1,2
这三种值,又比如timeUnit
字段我们只允许前端传给我们m,h,d
这三种值,这种场景经常遇到,平时做项目可能对参数校验没有太多要求,但是在实际的工作过程中,参数校验是非常重要的。我们今天想要讲的是如何对参数的枚举值进行校验,在此之前先来看一下注解校验的使用方法。
2. 回顾注解校验的使用
1、导入依赖
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、使用注解对前端传入的参数进行校验
@Data
public class DocAddReqVo {
@NotBlank(message = "文档名称不能为空")
@Pattern(regexp = "^[^\"'&<>()+%\\\\]{0,95}$", message = "文档名称不能含有\"'&<>()+%这些特殊字符,且长度不能超过95个字符")
private String name;
@NotBlank(message = "分类id不能为空")
private String categoryId;
@NotEmpty(message = "文档标签不能为空")
private Set<String> tags;
@Length(max = 5000, message = "文本内容长度不能超过5000个字符")
@NotBlank(message = "文档内容不能为空")
private String content;
}
3、实体类上标记校验注解之后,一定要开启校验功能@Valid
@RestController
@Slf4j
@RequestMapping("/api/v1")
public class DocController implements CommonConstant {
@Autowired
private DocService docService;
// 标注@Valid注解
@PostMapping("/docs")
public DocAddRespVo add(@RequestBody @Valid DocAddReqVo docAddReqVo) {
Doc doc = docService.addDoc(new Doc(docAddReqVo));
return new DocAddRespVo(doc.getId());
}
}
整个注解校验的过程就是这样,当然还有其他很多注解和使用方法,这不是我们今天关注的重点,不再延伸。
3. 自定义注解校验整型枚举值
需求:假如有一个字段status用来标识商品的上下架,上架代表0,下架代表1,规定前端给我们传入的参数只能是0,1
这两个值,而不能传入3,4,5等其他值,否则校验不通过,因此可以实现一个注解。
目标:实现注解 @AllowValue
,这个注解有一个属性vals,vals只能传入0,1
1、自定义一个注解@AllowValue
:
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 需要自定义一个校验器AllowConstraintValidator
@Constraint(validatedBy = { AllowConstraintValidator.class })
public @interface AllowValue {
// 检验失败的提示信息
String message() default "{}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 注解的属性
int[] vals() default {};
}
**2、自定义一个校验器AllowConstraintValidator **
public class AllowConstraintValidator implements ConstraintValidator<AllowValue, Integer> {
// @AllowValue(vals = { 0, 1 }, message = "status只能为0和1"),将校验的枚举值放进set中
Set<Integer> set = new HashSet<>();
@Override
public void initialize(AllowValue constraintAnnotation) {
// 注解的属性
int[] vals = constraintAnnotation.vals();
for (int value : vals) {
set.add(value);
}
}
/**
* 判断是否校验成功
* @param value 要校验的值
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 如果set中包含要校验的值,说明校验通过,否则校验失败
return value == null || set.contains(value);
}
}
3、使用注解校验
@Data
public class AttachmentAddReqVo {
@NotNull(message = "种类不能为空")
// vals = { 0, 1 },代表只允许前端传入0和1这两个值
@AllowValue(vals = { 0, 1 }, message = "status只能为0和1")
private int status;
}
上面的自定义注解vals是整型数组,只能校验整型值,那如果我们既想校验整型值,又想校验字符串,怎么办呢?就需要更通用的注解校验
4. 自定义注解校验各种类型枚举值
假如有一个字段status用来标识商品的上下架,上架代表0,下架代表1
4.1 使用示例1
需求:规定前端给我们传入的参数只能是0、1
这两个值,而不能传入其他值(null值也不允许),否则校验不通过
1、定义一个枚举类,枚举出来前端允许掺入的值:0和1
public enum AllowIntValueEnum {
/**
* 商品上下架枚举类
*/
UP(0),
DOWN(1);
private int value;
AllowIntValueEnum(int value){
this.value = value;
}
public int getValue() {
return value;
}
}
2、使用自定义注解@Enum校验:
@Data
public class DemoEntity {
// clazz : 枚举类的名称
// method :枚举类中获取要校验字段的方法名
// allowNull : 是否允许null值
// message:校验不通过时的提示信息
@Enum(clazz = AllowIntValueEnum.class,method = "getValue",allowNull = false,message = "只允许传入枚举值0和1")
private int status;
}
4.2 使用示例2
需求:规定前端给我们传入的参数只能是上架、下架
这两个值,而不能传入其他值(null值允许),否则校验不通过
1、定义一个枚举类,枚举出来前端允许传入的值:上架和下架
public enum AllowStringValueEnum {
/**
* 商品上下架枚举类
*/
UP("上架"),
DOWN("下架");
private String message;
AllowStringValueEnum(String message){
this.message = message;
}
public String getMessage() {
return message;
}
}
2、使用自定义注解@Enum校验:
@Data
public class DemoEntity {
// clazz : 枚举类的名称
// method :枚举类中获取要校验字段的方法名
// allowNull : 是否允许null值
// message:校验不通过时的提示信息
@Enum(clazz = AllowStringValueEnum.class,method = "getMessage",allowNull = true,message = "只允许传入枚举值上架和下架")
private String name;
}
4.3 使用示例3
需求:规定前端给我们传入的参数只能是上架、下架
这两个值,而不能传入其他值(null值不允许),否则校验不通过
1、定义一个枚举类,枚举出来前端允许传入的值:上架和下架
public enum AllowValueEnum {
/**
* 商品上下架枚举类
*/
UP(0,"上架"),
DOWN(1,"下架");
private int value;
private String message;
AllowValueEnum(int value,String message){
this.value = value;
this.message = message;
}
public int getValue() {
return value;
}
public String getMessage() {
return message;
}
}
2、使用自定义注解@Enum校验:
@Data
public class DemoEntity {
// clazz : 枚举类的名称
// method :枚举类中获取要校验字段的方法名
// allowNull : 是否允许null值
// message:校验不通过时的提示信息
@Enum(clazz = AllowValueEnum.class,method = "getMessage",allowNull = false,message = "只允许传入枚举值上架和下架")
private String status;
}
怎么样,看了上面的校验方式,是不是感觉很方便?那么这个注解是如何实现的呢?一起看看吧
4.4 实现自定义注解
1、自定义一个注解 @Enum
@Documented
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Enum.List.class)
@Constraint(validatedBy = { EnumValidator.class })
public @interface Enum {
String message() default "{*.}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* 要校验的枚举类
*
* @return 枚举类
*/
Class<?> clazz();
/**
* 要校验枚举类的哪个字段,这个字段通过getxxx()方法获取,返回方法的名称
*
* @return 方法的名称
*/
String method() default "ordinal";
/**
* 是否允许null值,默认是允许
*/
boolean allowNull() default true;
@Documented
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@interface List {
Enum[] value();
}
}
2、自定义一个注解校验器 EnumValidator
public class EnumValidator implements ConstraintValidator<Enum,Object> {
private Enum annotation;
@Override
public void initialize(Enum constraintAnnotation) {
this.annotation = constraintAnnotation;
}
/**
* 判断是否校验成功
* @param value 待校验的值
* @return 校验结果
*/
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
// 如果待校验的值为null,是否校验通过
if (value == null) {
return annotation.allowNull();
}
// 返回枚举常量的数组:UP,DOWN
Object[] objects = annotation.clazz().getEnumConstants();
try {
// 获取枚举类中获取校验字段的方法名
Method method = annotation.clazz().getMethod(annotation.method());
for (Object o : objects) {
// 将方法执行的结果(o)和待校验的值value进行比较,如果相同,说明校验成功
if (value.equals(method.invoke(o))) {
return true;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
// 如果value没有在枚举值中,校验失败
return false;
}
}
5. 自定义注解校验整型和字符串类型枚举值
如果不想写枚举类,又想校验字符串类型枚举值,也是可以的。
1、自定义注解
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { EnumValueValidator.class })
public @interface EnumValue {
// 默认错误消息
String message() default "必须为指定值";
// 字符串类型
String[] strValues() default {};
// 整型
int[] intValues() default {};
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
// 指定多个时使用
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
EnumValue[] value();
}
}
2、自定义校验器
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
private String[] strValues;
private int[] intValues;
@Override
public void initialize(EnumValue constraintAnnotation) {
strValues = constraintAnnotation.strValues();
intValues = constraintAnnotation.intValues();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (Objects.isNull(value)) {
return true;
}
if (value instanceof String) {
for (String s : strValues) {
if (s.equals(value)) {
return true;
}
}
} else if (value instanceof Integer) {
for (Integer s : intValues) {
if (s == value) {
return true;
}
}
}
return false;
}
}
3、使用自定义注解校验
@Data
public class DemoEntity {
@EnumValue(strValues = { "上架", "下架"},message = "只允许传入枚举值上架和下架")
private String name;
}