Skip to content

Knife4j和Swagger3注解使用与SpringBoot各种参数校验

1、Knife4j和Swagger3注解使用

1、Knife4j是什么

Knife4j是一个集Swagger3OpenAPI3 为一体的增强解决方案

2、pom.xml

spring-boot-param-validation

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xx</groupId>
    <artifactId>spring-boot-param-validation</artifactId>
    <version>1.0-SNAPSHOT</version>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.8</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.4.0</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.19</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

</project>

3、application.yml

yaml
server:
  port: 8080
  servlet:
    context-path: /param

4、启动类

java
package com.xx;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.util.StopWatch;

import java.net.InetAddress;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 09:11
 */
@SpringBootApplication
@Slf4j
public class PramValidationApplication {

    @SneakyThrows
    public static void main(String[] args){
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext application = SpringApplication.run(PramValidationApplication.class, args);
        stopWatch.stop();
        Environment env = application.getEnvironment();
        String ip = InetAddress.getLocalHost().getHostAddress();
        String port = env.getProperty("server.port");
        String path = env.getProperty("server.servlet.context-path");
        log.info("\n--------------------------------------------------------\n\t" +
                "Application Manager is running! Access URLs:\n\t" +
                "Local: \t\thttp://127.0.0.1:" + port + path + "/\n\t" +
                "External: \thttp://" + ip + ":" + port + path + "/\n\t" +
                "Swagger文档: \thttp://" + ip + ":" + port + path + "/doc.html\n\t" +
                "服务启动完成,耗时: \t" + stopWatch.getTotalTimeSeconds() + "S\n" +
                "----------------------------------------------------------");
    }
}

使用 @SneakyThrows 注解时,需要注意以下几点:

@SneakyThrows 注解只能用于方法上,不能用于字段、构造函数等其他地方。 方法上使用了 @SneakyThrows 注解后,编译器会忽略该方法中的受检查异常,并自动生成异常抛出的代码。

使用 @SneakyThrow注解时要谨慎,确保在方法中的异常处理逻辑充分而且合理。因为异常被转换为运行时异常,可能会隐藏原始的异常信息,增加调试的难度。

@SneakyThrows 注解可以配合使用多个异常类型,比如 @SneakyThrows({IOException.class, InterruptedException.class})。

需要注意的是,Lombok 是一个Java库,用于通过注解自动消除样板代码。它可以减少代码量,提高开发效率,但在使用之前,请确保已经熟悉并理解所使用的注解的作用和影响。

5、修改主界面信息

image-20240304092503693

java
package com.xx.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 09:33
 */
@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("小薛博客官方文档")
                        .version("1.0")
                        .description( "`我是小薛博客官方文档`,**你知道吗**")
                        .termsOfService("https://blog.xueqimiao.com/")
                        .license(new License().name("Apache 2.0")
                                .url("https://blog.xueqimiao.com/")));
    }
}

修改扫包

yaml
springdoc:
  group-configs:
    - group: 'xx'
      paths-to-match: '/**'
      # 生成文档所需的扫包路径,一般为启动类目录 可以不配置 会自动识别
      packages-to-scan: com.xx.controller

6、@Tag与@Operation

java
package com.xx.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 09:13
 */

@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
public class UserController {

    @Operation(summary = "创建用户",description = "根据姓名创建用户")
    @GetMapping("/create")
    public ResponseEntity<String> create(String name){
        return ResponseEntity.ok(name);
    }

}

image-20240304100956181

7、@Schema

java
package com.xx.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 09:13
 */
@Data
public class UserDTO implements Serializable {

    @Schema(title = "userId", description = "主键id", defaultValue = "1")
    private String id;

    @Schema(description = "名称", defaultValue = "张飞")
    private String name;

    @Schema(description = "年龄", defaultValue = "18", hidden = true)
    private String age;
}
java
    @Schema(description = "状态", allowableValues = {"Y", "N"})
    private String validStatus;
image-20240808090743579
java
@Operation(summary = "创建用户-createOne",description = "根据姓名创建用户1")
@PostMapping("/createOne")
public ResponseEntity<UserDTO> createOne(@RequestBody UserDTO user){
    return ResponseEntity.ok(user);
}

8、@Parameter

java
@Operation(summary = "获取用户信息", description = "根据id获取用户信息")
@PostMapping("/getUserById")
@Parameter(name = "id", description = "用户id", in = ParameterIn.QUERY, required = true, example = "6")
public ResponseEntity<String> getUserById(String id) {
    return ResponseEntity.ok(id);
}
java
@Operation(summary = "获取用户信息", description = "根据姓名、年龄获取用户信息")
@PostMapping("/getUserByNameAndAge")
@Parameters({
        @Parameter(name = "id", description = "用户id", in = ParameterIn.QUERY, required = true, example = "6"),
        @Parameter(name = "name", description = "用户姓名", in = ParameterIn.QUERY, required = true, example = "00")
})
public ResponseEntity<String> getUserByNameAndAge(String id, String name) {
    return ResponseEntity.ok(id);
}

9、接口添加作者

需要通过配置yml配置文件开启增强功能

yaml
knife4j:
  enable: true

接口上:

java
@ApiOperationSupport(author = "xx")
@Operation(summary = "创建用户", description = "根据姓名创建用户")
@GetMapping("/create")
public ResponseEntity<String> create(String name) {
    return ResponseEntity.ok(name);
}

Controller上:

java
@ApiSupport(author = "xxxx")

所代表的意思是该Controller模块下所有的接口都是该作者负责开发,当然用@ApiOperationSupport的注解也能覆盖

10、生产环境关闭文档

yaml
knife4j:
  enable: true
  # 开启生产环境屏蔽
  production: true

11、Basic认证功能

yaml
knife4j:
  enable: true
  # 开启生产环境屏蔽
#  production: true
  # 开启Swagger的Basic认证功能,默认是false
  basic:
    enable: true
    # Basic认证用户名
    username: test
    # Basic认证密码
    password: 123

12、接口排序

排序规则是使用Knife4j提供的增强注解@ApiOperationSupport中的order字段

java
package com.xx.controller;

import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.xx.dto.UserDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 09:13
 */

@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
@ApiSupport(author = "xxxx")
public class UserController {


    @ApiOperationSupport(author = "xx",order = 1)
    @Operation(summary = "创建用户", description = "根据姓名创建用户")
    @GetMapping("/create")
    public ResponseEntity<String> create(String name) {
        return ResponseEntity.ok(name);
    }

    @ApiOperationSupport(author = "xx",order = 2)
    @Operation(summary = "创建用户-createOne", description = "根据姓名创建用户1")
    @PostMapping("/createOne")
    public ResponseEntity<UserDTO> createOne(@RequestBody UserDTO user) {
        return ResponseEntity.ok(user);
    }

    @ApiOperationSupport(author = "xx",order = 3)
    @Operation(summary = "获取用户信息", description = "根据id获取用户信息")
    @PostMapping("/getUserById")
    @Parameter(name = "id", description = "用户id", in = ParameterIn.QUERY, required = true, example = "6")
    public ResponseEntity<String> getUserById(String id) {
        return ResponseEntity.ok(id);
    }

    @ApiOperationSupport(author = "xx",order = 4)
    @Operation(summary = "获取用户信息", description = "根据姓名、年龄获取用户信息")
    @PostMapping("/getUserByNameAndAge")
    @Parameters({
            @Parameter(name = "id", description = "用户id", in = ParameterIn.QUERY, required = true, example = "6"),
            @Parameter(name = "name", description = "用户姓名", in = ParameterIn.QUERY, required = true, example = "00")
    })
    public ResponseEntity<String> getUserByNameAndAge(String id, String name) {
        return ResponseEntity.ok(id);
    }
}

2、SpringBoot参数校验

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>com.xx</groupId>
    <artifactId>xx-common-core</artifactId>
    <version>1.4.0</version>
<dependency>

1、非空校验

JAVA
@NotNull(message = "用户id不能为空")
private String id;

@NotBlank(message = "用户名不能为空")
private String name;

@NotEmpty(message = "houseIds不能为空")
private List<Integer> houseIds;
java
@Operation(summary = "创建用户-createOne", description = "根据姓名创建用户1")
@PostMapping("/createOne")
public ResponseEntity<UserDTO> createOne(@RequestBody @Validated UserDTO user) {
    return ResponseEntity.ok(user);
}

@NotNull: 它用于标记一个属性或方法参数不能为空。它适用于任何类型的参数,包括字符串、集合、数组等。如果一个参数被标记为 @NotNull,在校验过程中,如果该参数的值为 null,将会触发校验失败,并返回相应的错误信息。

@NotBlank: 它用于标记一个字符串类型的属性或方法参数不能为空,并且不能只包含空格。它会先对参数进行 @NotNull 的非空校验,然后再对字符串进行额外的校验。如果参数的值为 null 或者只包含空格,将会触发校验失败,并返回相应的错误信息。

@NotEmpty:用于限制集合、数组、Map 等类型属性值不能为 null 或空。

JSR-303 是 Java Specification Request 303 的缩写,它定义了 Java 中用于对象校验的标准规范,即 Bean Validation 规范。

JSR-380 是 Java Specification Request 380 的缩写,它是 JSR-303 规范的升级版,也被称为 Bean Validation 2.0 规范。

2、自定义异常

https://xiaoxueblog.com/springboot/08、全局异常处理.html

java
package com.xx.handler;

import com.xx.common.Result;
import com.xx.common.ResultCodeEnum;
import com.xx.utils.FunctionUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 11:31
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    /**
     * 集中处理参数丢失、缺少参数、参数为空 情况异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public Result<?> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        List<String> messageMap = FunctionUtils.map(fieldErrors, FieldError::getDefaultMessage);
        // 逗号拼接
        String message = String.join(",", messageMap);
        return Result.error(ResultCodeEnum.PARAM_VERIFICATION_FAIL.getCode(), message);
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.error("操作失败," + e.getMessage());
    }
}

image-20240304113737326

3、长度校验

java
@NotBlank(message = "用户名不能为空")
@Length(min = 2, max = 10, message = "用户名长度必须在2-10之间")
private String name;

4、范围校验

java
// 数字

@Min(value = 1, message = "年龄不能小于1")
@Max(value = 100, message = "年龄不能大于100")
private Integer age;

@Range(min = 1, max = 100, message = "年龄2必须在1到100之间")
private Integer age2;
java
// 金额

@NotNull(message = "金额不能为空")
@DecimalMin(value = "0.01", message = "金额不能小于0.01")
@DecimalMax(value = "10.00", message = "金额不能大于10.00")
private BigDecimal amount;
java
// 集合

@Size(min = 1, max = 3, message = "houseIds长度必须在1-3之间")
private List<Integer> houseIds;
java
// 日期

@Future(message = "日期必须是未来的日期")
private Date futureDate;

@Past(message = "日期必须是过去的日期")
private Date pastDate;

5、拓展SpringBoot日期格式化

java
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date futureDate;

@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date pastDate;

6、正则校验

java
@Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确")
private String phone;

7、邮箱校验

java
@Email(message = "邮箱格式不正确")
private String email;

8、RequestParam/PathVariable参数校验

添加全局异常

java
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public Result<?> constraintViolationException(HttpServletRequest request, ConstraintViolationException ex) {
    Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
    List<String> messageList = new ArrayList<>();
    constraintViolations.forEach(constraintViolation -> {
        log.error("参数验证失败:{}", constraintViolation.getMessage());
        messageList.add(constraintViolation.getMessage());
    });
    return Result.error(ResultCodeEnum.PARAM_VERIFICATION_FAIL.getCode(), String.join(",", messageList));
}
java
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
@ApiSupport(author = "xxxx")
@Validated // 记得加
public class UserController {

    @ApiOperationSupport(author = "xx",order = 3)
    @Operation(summary = "获取用户信息", description = "根据id获取用户信息")
    @GetMapping("{userId}")
    public ResponseEntity<Integer> getById(@PathVariable("userId") @Min(value = 1,message = "用户id不能小于1") Integer userId) {
        return ResponseEntity.ok(userId);
    }
  
    @ApiOperationSupport(author = "xx",order = 3)
    @Operation(summary = "获取用户信息2", description = "根据id获取用户信息")
    @GetMapping("/getById2")
    public ResponseEntity<Integer> getById2(@RequestParam("userId") @Min(value = 1,message = "用户id不能小于1") Integer userId) {
        return ResponseEntity.ok(userId);
    }
  
}

9、分组校验

通常新增用户修改用户会使用同一个DTO对象,但是id只有在修改的时候必传新增的时候不需要

新增 增删改查 分组

java
package com.xx.validation.group;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:23
 */
public interface SaveGroup {
}
java
package com.xx.validation.group;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:24
 */
public interface DeleteGroup {
}
java
package com.xx.validation.group;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:24
 */
public interface UpdateGroup {
}
java
package com.xx.validation.group;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:24
 */
public interface SelectGroup {
}

@Validated(SaveGroup.class)

@Validated(UpdateGroup.class)

java
@ApiOperationSupport(author = "xx",order = 2)
@Operation(summary = "创建用户-createOne", description = "根据姓名创建用户1")
@PostMapping("/createOne")
public ResponseEntity<UserDTO> createOne(@RequestBody @Validated(SaveGroup.class) UserDTO user) {
    return ResponseEntity.ok(user);
}

@ApiOperationSupport(author = "xx",order = 2)
@Operation(summary = "修改用户", description = "根据姓名创建用户")
@PostMapping("/updateOne")
public ResponseEntity<UserDTO> updateOne(@RequestBody @Validated(UpdateGroup.class) UserDTO user) {
    return ResponseEntity.ok(user);
}
java
@NotNull(message = "用户id不能为空",groups = {UpdateGroup.class})
private String id;

10、嵌套校验

java
package com.xx.dto;

import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:36
 */
@Data
public class WorkDTO {

    @NotBlank(message = "公司名称不能为空")
    private String company;

    @DecimalMin(value = "0.01", message = "工资不能小于0.01")
    @DecimalMax(value = "10.00", message = "工资不能大于10.00")
    private BigDecimal salary;
}
java
// UserDTO 添加属性

@Schema(description = "工作信息")
@NotNull(message = "工作信息不能为空")
@Valid
private WorkDTO workDTO;
java
@ApiOperationSupport(author = "xx",order = 2)
@Operation(summary = "创建用户-createOne2", description = "根据姓名创建用户2")
@PostMapping("/createOne2")
public ResponseEntity<UserDTO> createOne2(@RequestBody @Validated UserDTO user) {
    return ResponseEntity.ok(user);
}

需要注意的是,此时DTO类的对应字段必须标记@Valid注解

11、自定义校验实现只能输入特定的枚举值

java
package com.xx.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:47
 */
public class ConstantEnumValueValidator implements ConstraintValidator<ConstantEnumValue, Object> {

    private String[] strValues;
    private int[] intValues;

    @Override
    public void initialize(ConstantEnumValue constraintAnnotation) {
        strValues = constraintAnnotation.strValues();
        intValues = constraintAnnotation.intValues();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value instanceof String) {
            for (String s : strValues) {
                if (s.equals(value)) {
                    return true;
                }
            }
        } else if (value instanceof Integer) {
            for (int s : intValues) {
                if (s == ((Integer) value).intValue()) {
                    return true;
                }
            }
        }
        return false;
    }
}
java
package com.xx.validation;

import com.baomidou.mybatisplus.annotation.EnumValue;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:47
 */
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {ConstantEnumValueValidator.class})
public @interface ConstantEnumValue {

    // 默认错误消息
    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();
    }
}
java
@ConstantEnumValue(strValues = {"Y", "N"}, message = "属性状态只能为Y或者N")
private String validStatus;

12、自定义校验实现日期范围

java
package com.xx.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:54
 */
@Documented
@Constraint(validatedBy = DateRangeValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateRange {

    String message() default "日期超出范围";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    String min(); // 最小日期
    String max(); // 最大日期

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        DateRange[] value();
    }
}
java
package com.xx.validation;

import com.xx.utils.DateTimeUtil;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Date;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/4 13:51
 */
public class DateRangeValidator implements ConstraintValidator<DateRange, Date> {

    private Date minDate;
    private Date maxDate;

    @Override
    public void initialize(DateRange constraintAnnotation) {
        this.minDate = DateTimeUtil.parseToDate(constraintAnnotation.min());
        this.maxDate = DateTimeUtil.parseToDate(constraintAnnotation.max());

    }

    @Override
    public boolean isValid(Date value, ConstraintValidatorContext context) {
        if (value == null) {
            return true; // 允许为空
        }
        return value.compareTo(minDate) >= 0 && value.compareTo(maxDate) <= 0;
    }
}
java
@DateRange(min = "2000-01-01", max = "2023-01-01", message = "生日超出范围")
private Date birthday;

13、自定义校验枚举

之前的常量校验弊端 比如加了一个状态 字段上也得跟着改

java
package com.xx.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

/**
 * @Author: xueqimiao
 * @Date: 2024/8/8 09:14
 */
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {EnumFieldValidator.class})
public @interface EnumFieldValid {

    // 默认错误消息
    String message() default "必须为指定的枚举值";

    // 枚举类
    Class<? extends Enum<?>> enumClass();

    // 枚举字段方法
    String enumFieldMethod();

    // 是否允许为空
    boolean allowNull() default false;

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};
}
java
package com.xx.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.lang.reflect.Method;

/**
 * @Author: xueqimiao
 * @Date: 2024/8/8 09:14
 */

public class EnumFieldValidator implements ConstraintValidator<EnumFieldValid, Object> {

    private Class<? extends Enum<?>> enumClass;
    private Method enumFieldMethod;

    private boolean allowNull;

    @Override
    public void initialize(EnumFieldValid constraintAnnotation) {
        enumClass = constraintAnnotation.enumClass();
        allowNull = constraintAnnotation.allowNull();
        try {
            enumFieldMethod = enumClass.getMethod(constraintAnnotation.enumFieldMethod());
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("指定的枚举字段方法不存在", e);
        }
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            return allowNull; // 允许空值时返回 true,不允许时返回 false
        }
        try {
            for (Enum<?> enumConstant : enumClass.getEnumConstants()) {
                Object enumFieldValue = enumFieldMethod.invoke(enumConstant);
                // Debug information
                System.out.println("Comparing value: " + value + " with enum field value: " + enumFieldValue);
                if (enumFieldValue != null && String.valueOf(enumFieldValue).equals(value)) {
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            throw new RuntimeException("枚举验证过程中发生错误", e);
        }
    }
}
java
@EnumFieldValid(enumClass = StatusEnum.class, enumFieldMethod = "getCode", message = "状态码必须为1或2")
@Schema(description = "状态", allowableValues = {"1", "2"})
private String validStatus;

14、@Valid和@Validated区别

区别@Valid@Validated
提供者JSR-303规范Spring
是否支持分组不支持支持
嵌套校验支持不支持

写在最后

好了,今天关于技术探索、学习和项目难点的分享就到这里啦!我知道,很多小伙伴在技术的道路上可能会遇到各种各样的问题,有时候会觉得迷茫,有时候会想要放弃。但是我想说,别灰心,别气馁!

咱们都是从不会到会,从做不好到能做好的。就像爬山一样,有时候会觉得路途艰辛,气喘吁吁,但只要一步一个脚印地往上走,最终一定能登上山顶,看到那美丽的风景。

所以啊,不管是学习新技术,还是做个人项目,只要咱们保持那股子热情和劲头,遇到问题多琢磨琢磨,多尝试尝试,总会把问题解决的。相信自己,你就是那个未来的技术大佬!加油吧!