SpringBoot3.x使用Swagger

SpringBoot3.x使用Swagger

  • 当前开发主流是前后端分离,有完整文档可以使团队配合更加流畅
  • Spring生态中通常使用springfox,但是当前springfox并不支持SpringBoot3.x版本
  • 使用替代产品:Springdoc.org
    • Springdoc在v1.7.0版本之后不支持SpringBoot2.x和1.x!!!
  • 项目启动后,Swagger默认地址:http://localhost:8080/swagger-ui/index.html
  • 版本:
    • Java: 17.0.7
    • SpringBoot: 3.1.5
    • springdoc: 2.2.0
  • 同步发布BiliBili视频

设置

  • Springdoc同时支持WebMvc和WebFlux
  • 因为没用WebFlux实践过,所以下面只介绍WebMvc中的应用

依赖(pom.xml)

  • 实际开发中,通常会和校验搭配使用
xml 复制代码
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>
<!--  搭配校验使用,使用与SpringBoot相同的版本号  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>3.1.5</version>
</dependency>
  • 示例中使用的其他依赖
xml 复制代码
<!--  SpringBoot  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.1.5</version>
</dependency>
<!-- 让响应结果更美观 -->
<dependency>
    <groupId>com.alibaba.cola</groupId>
    <artifactId>cola-component-dto</artifactId>
    <version>4.3.2</version>
</dependency>
<!--  lombok  -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
    <optional>true</optional>
</dependency>
<!--  日志  -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>

环境配置(application.yml)

  • 开发环境
  • 开发环境通常会开启Swagger文档,方便前端查阅文档
  • 如果使用微服务,为避免Swagger地址冲突,通常会加上前缀
    • 如鉴权服务: "/auth-service/v3/api-docs"和"/auth-service/swagger-ui/index.html"
    • 如用户服务: "/user-service/v2/api-docs"和"/user-service/swagger-ui/index.html"
yml 复制代码
springdoc:
  api-docs:
    enabled: true # 开启OpenApi接口
    path: /user-service/v3/api-docs  # 自定义路径,默认为 "/v3/api-docs"
  swagger-ui:
    enabled: true # 开启swagger界面,依赖OpenApi,需要OpenApi同时开启
    path: /user-service/swagger-ui/index.html # 自定义路径,默认为"/swagger-ui/index.html"
  • 生产环境
  • 切记生产环境要关闭文档
yml 复制代码
springdoc:
  api-docs:
    enabled: false # 关闭OpenApi接口
  swagger-ui:
    enabled: false # 关闭swagger界面

配置

  • 在SwaggerUI中增加描述
  • 项目中新建"SwaggerConfig.java"文件
java 复制代码
@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI swaggerOpenApi() {
        return new OpenAPI()
                .info(new Info().title("XXX平台YYY微服务")
                        .description("描述平台多牛逼")
                        .version("v1.0.0"))
                .externalDocs(new ExternalDocumentation()
                        .description("设计文档")
                        .url("https://juejin.cn/user/254742430749736/posts"));
    }
}
  • 全局异常处理
  • 参数校验错误返回提示信息
  • 捕捉Exception大类,后面示例中补充更详细的类
  • 响应结果Response为"cola-component-dto"中的一个包装类
java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public Response handleException(Exception e) {
        log.warn("未知异常", e);
        return Response.buildFailure("未知异常", e.getMessage());
    }
}

Restful接口

Post

  • Post用于新增
  • 通常使用body传递参数

用户类

  • 新增用户模型,说明
    • @Data: Lombok的写法,可以省略get/set
    • @Schema: Swagger文档的注解,用于说明类/字段
      • title: 类/字段说明
      • example: 示例,Swagger中会将这个字段作为示例
      • minLength/maxLength: 最小/最大长度,字段为String类型时生效(仅用于文档说明,不会抛出异常)
      • minimum/maximum: 最小/最大值,字段为数字时有效(仅用于文档说明,不会抛出异常)
    • @NotBlank: 校验不能为空(String生效),为空或空字符则抛出异常。文档将该字段解析为必填项
    • @NotNull: 校验不能为空(包装类生效,如Integer/Long/Boolean),为空将抛出异常。文档将该字段解析为必填项
    • @Range: 检查数值范围,不符合将抛出异常
java 复制代码
@Data
@Schema(title = "新增用户模型")
public class UserAddCO {
    @Schema(title = "名字", example = "老王", minLength = 1, maxLength = 5)
    @NotBlank(message = "名字不能为空")
    private String name;

    @Schema(title = "年龄", example = "18", minimum = "0", maximum = "150")
    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 150, message = "年龄在0~150之间")
    private Integer age;
//    private int age;
//    既然不能为空,为什么不使用int?
//    1. 因为int是基本类型不会为空,所以@NotNull校验无效
//    2. 类初始化时生成默认值(int默认为0),@Range中最小值包含0,所以没有age参数校验通过,不符合预期

    @Schema(title = "电话(可选)")
    private String phone;
}

请求方法

  • @Tag: 控制器说明
    • name: 名称
    • description: 描述说明
  • @PostMapping: 使用post方法,一般用于新增记录
  • @Operation: 请求说明
    • summary: 说明,Swagger页面在方法后面,不会被折叠
    • descirption: 描述,会被折叠到方法说明中
  • @Validated: 校验数据,有了这个注解模型中的@NotBlank@NotNull@Range才会生效
  • @RequestBody: 从请求body中读取数据
java 复制代码
@RestController
@RequestMapping("/demo")
@Tag(name = "示例控制器", description = "演示Restful接口")
public class DemoController {
    @PostMapping("/")
    @Operation(summary = "Post方法示例", description = "Post通常用于新增")
    public SingleResponse<Long> add(@Validated @RequestBody UserAddCO user) {
        // TODO:添加到数据库中,然后返回记录id
        return SingleResponse.of(1L);
    }
}

异常处理

  • BindException: 如@NotBlank@NotNull@Range等校验失败时会报该异常
  • HttpMessageConversionException: 转换失败时,如age参数传入字符串会报该异常
java 复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Response handleBindException(BindException e) {
        // 拼接错误信息,用于多个校验不通过的错误信息拼接
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(";"));
        log.info("参数校验不通过:{}", message);
        return Response.buildFailure("参数校验不通过", message);
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageConversionException.class)
    public Response handleHttpMessageConversionException(HttpMessageConversionException e) {
        log.info("参数转换失败:{}", e.getMessage());
        return Response.buildFailure("参数转换失败", e.getMessage());
    }
}

Post(FormData)

  • 上传通常使用FormData上传数据
  • @PostMapping: 上传使用Post
    • consumes: 这个值一定要设置成"MediaType.MULTIPART_FORM_DATA_VALUE",否则Swagger将错误识别为json格式(file字段错误识别为string),而不是FormData
  • @RequestPart: 比较少用到,就是用于FormData
  • MultipartFile: 接收文件的对象,可以将流保存到本地
java 复制代码
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上传文件示例")
    public SingleResponse<String> upload(@RequestPart("file") MultipartFile file) {
        log.info("接收到文件:{}, 大小:{}", file.getOriginalFilename(), file.getSize());
        try {
            // 保存到本地
            File localTempFile = new File(file.getOriginalFilename());
            localTempFile.createNewFile();
            file.transferTo(localTempFile);
            return SingleResponse.of(localTempFile.getAbsolutePath());
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败");
        }
    }

    @PostMapping(value = "/upload/multi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上传多个文件示例")
    public MultiResponse<String> uploadMulti(@RequestPart("files") MultipartFile[] files) {
        List<String> result = new LinkedList<>();
        for (MultipartFile file : files) {
            log.info("接收到文件:{}, 大小:{}", file.getOriginalFilename(), file.getSize());
            result.add(file.getOriginalFilename());
            // TODO:如上保存到本地
        }
        return MultiResponse.of(result);
    }

Get(分页)

  • Get常用于获取分页数据

用户详情类(响应结果)

  • 用户详情
  • @Schema: 字段/类的Swagger描述注解
    • title: 字段/类说明,实际开发中,这个是必填,其他为可选
    • 如example/minLength/maxLength/minimum/maximum等为可选项,部分HTTP工具可以利用这些描述生成Mock数据
java 复制代码
@Data
@Schema(title = "用户详情")
public class UserDetailCO {
    @Schema(title = "用户id", minimum = "1")
    private long id;

    @Schema(title = "用户名", minLength = 1, maxLength = 5, example = "言午日尧耳总")
    private String name;

    @Schema(title = "手机")
    private String phone;
}

请求分页类

  • @Parameter: QueryString的参数定义
    • required: 是否必填
    • description: 描述
    • example: 示例值,部分HTTP工具请求时会使用这个当做默认值(开发自测调试,不用每次写参数)
java 复制代码
@Data
public class UserPageQry {
    @Parameter(required = true, description = "页码,从1开始", example = "1")
    @Min(value = 1, message = "pageIndex必须大于1")
    private int pageIndex;
//    @NotNull(message="pageIndex参数必须填写")
//    private Integer pageIndex;
//    1. 此处可以使用int类型,因为限制最小值为1,不填写默认赋值0,@Min校验不通过会抛出异常
//    2. 也可以写成Integer+@NotNull的组合(如下pageSize),更加语义化

    @Parameter(required = true, description = "页面大小", example = "20")
    @NotNull(message = "pageSize参数必须填写")
    @Range(min = 1, max = 100, message = "pageSize必须在1-100之间")
    private Integer pageSize;

    @Parameter(description = "搜索名字(模糊搜索),不搜索就传null或空字符")
    private String phone;
}

请求方法1(推荐)

  • Operation: 请求描述
  • @Validated: 启用校验
  • @ParameterObject: 参数对象,这个注解可以将对象字段解析为QueryString参数
  • PageResponse: 分页响应结果,Swagger中解析响应字段
java 复制代码
    @GetMapping("/")
    @Operation(summary = "获取分页列表")
    public PageResponse<UserDetailCO> getPageList(@Validated @ParameterObject UserPageQry qry) {
        log.info("{}", qry);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, qry.getPageSize(), qry.getPageIndex());
    }

请求方法2(不推荐,没有方法1优雅)

  • 字段单独设置
  • @Validated: 控制器上的@Validated必须设置,否则校验注解无效
  • @Operation: 方法说明
  • @Parameter: 对每一个字段进行描述
    • name: 与字段名一一对应,才能正确解析
    • description: 描述
    • example: 示例值,部分HTTP工具请求时会使用这个当做默认值(开发自测调试,不用每次写参数)
java 复制代码
@Slf4j
@Validated // 控制器加上改注解到才能进行校验
@RestController
@RequestMapping("/demo")
@Tag(name = "示例控制器", description = "演示Restful接口")
public class DemoController {
    @GetMapping("/other/")
    @Operation(summary = "另一种获取分页列表方式")
    @Parameter(name = "pageIndex", description = "页码,从1开始", example = "1")
    @Parameter(name = "pageSize", description = "数量,不小于1", example = "20")
    @Parameter(name = "name", description = "搜索名字(模糊搜索),不搜索就传null或空字符")
    public PageResponse<UserDetailCO> getPage(@NotNull(message = "pageIndex不能为空") @Min(value = 1, message = "pageIndex必须大于1") Integer pageIndex,
                                              @NotNull(message = "pageIndex不能为空") @Range(min = 1, max = 100, message = "pageSize必须在1-100之间") Integer pageSize,
                                              @Nullable String name) {
        log.info("{} {} {}", pageIndex, pageSize, name);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, pageSize, pageIndex);
    }
}
  • 异常处理
java 复制代码
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public Response handleValidationException(ValidationException e) {
        log.info("参数验证失败: {}", e.getMessage());
        return Response.buildFailure("参数验证失败", e.getMessage());
    }

Get(一条记录详情)

  • @GetMapping: Restful风格,在请求路径中放id
  • @PathVariable: 读取路径中的id
    • 接收参数可以使用long这样的基本类型,id必定不为空(如果为空,路径错误不会进入该方法)
java 复制代码
    @GetMapping("/{id}/")
    @Operation(summary = "获取用户详情")
    public SingleResponse<UserDetailCO> getUser(@PathVariable("id") long id) {
        log.info("{}", id);
        return SingleResponse.of(new UserDetailCO());
    }

Put/Patch

  • 区别
    • Put: 修改对象的所有参数
    • Patch: 修改对象的部分参数
  • id从路径中读取,遵循Restful风格
  • 值从body中读取
    • body接收对象UserPutCO包含User的所有字段,UserPatchCO只包含一部分字段
java 复制代码
    @PutMapping("/{id}/")
    @Operation(summary = "修改参数")
    public Response putUser(@PathVariable("id") long id, @Validated @RequestBody UserPutCO user) {
        // TODO:使用id在数据查找到用户,然后修改值并保存
        return Response.buildSuccess();
    }

    @PatchMapping("/{id}/")
    @Operation(summary = "修改部分参数")
    public Response patchUser(@PathVariable("id") long id, @Validated @RequestBody UserPatchCO user) {
        // TODO:使用id在数据查找到用户,然后修改值并保存
        return Response.buildSuccess();
    }

Delete

java 复制代码
    @DeleteMapping("/{id}/")
    @Operation(summary = "删除")
    public Response deleteUser(@PathVariable("id") long id) {
        // TODO:根据id直接删除用户
        return Response.buildSuccess();
    }

其他

屏蔽过滤器

  • 用于前后端分离项目
  • 有时候为了鉴权方便,会使用全局过滤器过滤权限
  • 将Swagger相关接口排除掉
java 复制代码
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Bean
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 除了swagger和登录,其他全部拦截验证jwt
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/**/*.html",
                        "/**/*.js",
                        "/**/*.css",
                        "/**/*.woff",
                        "/**/*.ttf",
                        "/**/*.js",
                        "/**/*.map",
                        "/**/*.png",
                        "/v3/api-docs", // 如果配置里改了,这里也记得修改
                        "/v3/api-docs/swagger-config",
                        "/auth/login"); // 登录接口
    }
}

不同风格注解

  • 将所有配置写在控制器中
java 复制代码
@Tag(name = "User", description = "用户管理")
@RestController
@RequestMapping("/user")
public class UserController {
    // 这个样式看起来更整齐
    @Operation(summary = "获取用户信息", description = "用户详情")
    @Parameter(name = "id", description = "用户id")
    @ApiResponse(responseCode = "200", description = "用户信息")
    @GetMapping("/{id}")
    public Resp<?> detail(@PathVariable("id") Integer id) {
        User user = new User();
        return new Resp<>(user);
    }

    @Operation(summary = "新增", description = "新增用户")
    @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "用户")
    @ApiResponse(responseCode = "201", description = "成功")
    @PostMapping("/")
    public Resp<User> add(@RequestBody User user) {
        return new Resp<>(user);
    }

    // 写到一起的示例
    @Operation(summary = "获取列表",
            description = "获取用户列表",
            parameters = {@Parameter(name = "page", description = "页码"),
                    @Parameter(name = "size", description = "每页数量")},
            responses = {@ApiResponse(responseCode = "200", description = "用户列表")})
    @GetMapping("/")
    public Resp<List<User>> list(@PathParam("page") Integer page, @PathParam("size") Integer size) {
        return new Resp<>(new ArrayList<>());
    }
}

使用Apifox自测

  • HTTP请求工具可以读取OpenApi(Swagger)生成HTTP请求
  • 以Apifox为例
    • 打开Apifox,新建项目
    • 项目设置 > 数据管理 > 导入数据 > URL方式导入
    • 填入:http://localhost:8080/v3/api-docs
    • 提交,即可直接导入API接口
    • 就会根据Swagger生成HTTP文档及请求

完整示例代码

  • Swagger设置
java 复制代码
@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI swaggerOpenApi() {
        return new OpenAPI()
                .info(new Info().title("XXX平台YYY服务")
                        .description("描述平台多牛逼")
                        .version("v0.0.1"))
                .externalDocs(new ExternalDocumentation()
                        .description("设计文档")
                        .url("https://juejin.cn/user/254742430749736/posts"));
    }
}
  • 异常处理
java 复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public Response handleException(Exception e) {
        log.warn("未知异常", e);
        return Response.buildFailure("未知异常", e.getMessage());
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Response handleBindException(BindException e) {
        // 拼接错误信息,用于多个校验不通过的错误信息拼接
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(";"));
        log.info("参数校验不通过:{}", message);
        return Response.buildFailure("参数校验不通过", message);
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageConversionException.class)
    public Response handleHttpMessageConversionException(HttpMessageConversionException e) {
        log.info("参数转换失败:{}", e.getMessage());
        return Response.buildFailure("参数转换失败", e.getMessage());
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public Response handleValidationException(ValidationException e) {
        log.info("参数验证失败: {}", e.getMessage());
        return Response.buildFailure("参数验证失败", e.getMessage());
    }
}
  • 控制器
java 复制代码
@Slf4j
@Validated
@RestController
@RequestMapping("/demo")
@Tag(name = "示例控制器", description = "演示Restful接口")
public class DemoController {
    @PostMapping("/")
    @Operation(summary = "Post方法示例", description = "Post通常用于新增")
    public SingleResponse<Long> add(@Validated @RequestBody UserAddCO user) {
        // TODO:添加到数据库中,然后返回记录id
        return SingleResponse.of(1L);
    }

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上传文件示例")
    public SingleResponse<String> upload(@RequestPart("file") MultipartFile file) {
        log.info("接收到文件:{}, 大小:{}", file.getOriginalFilename(), file.getSize());
        try {
            // 保存到本地
            File localTempFile = new File(file.getOriginalFilename());
            localTempFile.createNewFile();
            file.transferTo(localTempFile);
            return SingleResponse.of(localTempFile.getAbsolutePath());
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败");
        }
    }

    @PostMapping(value = "/upload/multi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上传多个文件示例")
    public MultiResponse<String> uploadMulti(@RequestPart("files") MultipartFile[] files) {
        List<String> result = new LinkedList<>();
        for (MultipartFile file : files) {
            log.info("接收到文件:{}, 大小:{}", file.getOriginalFilename(), file.getSize());
            result.add(file.getOriginalFilename());
            // TODO:如上保存到本地
        }
        return MultiResponse.of(result);
    }

    @GetMapping("/")
    @Operation(summary = "获取分页列表")
    public PageResponse<UserDetailCO> getPageList(@Validated @ParameterObject UserPageQry qry) {
        log.info("{}", qry);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, qry.getPageSize(), qry.getPageIndex());
    }

    @GetMapping("/other/")
    @Operation(summary = "另一种获取分页列表方式")
    @Parameter(name = "pageIndex", description = "页码,从1开始", example = "1")
    @Parameter(name = "pageSize", description = "数量,不小于1", example = "20")
    @Parameter(name = "name", description = "搜索名字(模糊搜索),不搜索就传null或空字符")
    public PageResponse<UserDetailCO> getPage(@NotNull(message = "pageIndex不能为空") @Min(value = 1, message = "pageIndex必须大于1") Integer pageIndex,
                                              @NotNull(message = "pageIndex不能为空") @Range(min = 1, max = 100, message = "pageSize必须在1-100之间") Integer pageSize,
                                              @Nullable String name) {
        log.info("{} {} {}", pageIndex, pageSize, name);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, pageSize, pageIndex);
    }

    @GetMapping("/{id}/")
    @Operation(summary = "获取用户详情")
    public SingleResponse<UserDetailCO> getUser(@PathVariable("id") long id) {
        // TODO:从数据库查询
        return SingleResponse.of(new UserDetailCO());
    }

    @PutMapping("/{id}/")
    @Operation(summary = "修改参数")
    public Response putUser(@PathVariable("id") long id, @Validated @RequestBody UserPutCO user) {
        // TODO:使用id在数据查找到用户,然后修改值并保存
        return Response.buildSuccess();
    }

    @PatchMapping("/{id}/")
    @Operation(summary = "修改部分参数")
    public Response patchUser(@PathVariable("id") long id, @Validated @RequestBody UserPatchCO user) {
        // TODO:使用id在数据查找到用户,然后修改值并保存
        return Response.buildSuccess();
    }

    @DeleteMapping("/{id}/")
    @Operation(summary = "删除")
    public Response deleteUser(@PathVariable("id") long id) {
        // TODO:根据id直接删除用户
        return Response.buildSuccess();
    }
}
  • 用户新增
java 复制代码
@Data
@Schema(title = "新增用户模型")
public class UserAddCO {
    @Schema(title = "名字", example = "老王", minLength = 1, maxLength = 5)
    @NotBlank(message = "名字不能为空")
    private String name;

    @Schema(title = "年龄", example = "18", minimum = "0", maximum = "150")
    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 150, message = "年龄在0~150之间")
    private Integer age;
//    private int age;
//    既然不能为空,为什么不使用int?
//    1. 因为int是基本类型不会为空,所以@NotNull校验无效
//    2. 类初始化时生成默认值(int默认为0),@Range中最小值包含0,所以没有age参数校验通过,不符合预期

    @Schema(title = "电话(可选)")
    private String phone;
}
  • 用户详情
java 复制代码
@Data
@Schema(title = "用户详情")
public class UserDetailCO {
    @Schema(title = "用户id", minimum = "1")
    private long id;

    @Schema(title = "用户名", minLength = 1, maxLength = 5, example = "言午日尧耳总")
    private String name;

    @Schema(title = "手机")
    private String phone;
}
  • 分页请求
java 复制代码
@Data
public class UserPageQry {
    @Parameter(required = true, description = "页码,从1开始", example = "1")
    @Min(value = 1, message = "pageIndex必须大于1")
    private int pageIndex;
//    @NotNull(message="pageIndex参数必须填写")
//    private Integer pageIndex;
//    1. 此处可以使用int类型,因为限制最小值为1,不填写默认赋值0,@Min校验不通过会抛出异常
//    2. 也可以写成Integer+@NotNull的组合(如下pageSize),更加语义化

    @Parameter(required = true, description = "页面大小", example = "20")
    @NotNull(message = "pageSize参数必须填写")
    @Range(min = 1, max = 100, message = "pageSize必须在1-100之间")
    private Integer pageSize;

    @Parameter(description = "搜索名字(模糊搜索),不搜索就传null或空字符")
    private String phone;
}
  • Patch修改用户
java 复制代码
@Data
@Schema(title = "新增用户模型")
public class UserPatchCO {
    @Schema(title = "电话(可选)")
    private String phone;
}
  • Put修改用户
java 复制代码
@Data
@Schema(title = "新增用户模型")
public class UserPutCO {
    @Schema(title = "名字", example = "老王", minLength = 1, maxLength = 5)
    @NotBlank(message = "名字不能为空")
    private String name;

    @Schema(title = "年龄", example = "18", minimum = "0", maximum = "150")
    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 150, message = "年龄在0~150之间")
    private Integer age;

    @Schema(title = "电话(可选)")
    private String phone;
}
相关推荐
皮皮林5516 小时前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602733 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840824 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解4 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解4 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记4 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者5 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840825 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解5 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者5 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq