Swagger3学习与实践指南

一、Swagger简介

出现的背景

在现代软件开发中,前后端分离的架构已成为主流。前端(Web、App)和后端(服务器)通过一套API进行通信。随着项目规模越来越大,API的数量也急剧增加,带来了一系列问题:

  • 沟通成本高:前端开发人员需要知道如何调用API,包括URL是什么、需要传什么参数、返回什么数据。后端开发人员通常通过编写Word或Markdown文档来告知前端,但这种方式效率低下且容易出错。
  • 文档维护难:代码在不断迭代,API也随之变化。如果API变更了,而文档没有及时更新,就会导致前后端联调时出现各种问题,浪费大量时间。
  • 测试不方便:开发人员需要一个简单快捷的方式来测试API接口,确保其功能符合预期。传统的测试方式通常需要借助Postman等第三方工具,操作相对繁琐。

Swagger,解决什么问题?

Swagger(现在其规范被称为 OpenAPI Specification )正是为了解决上述问题而诞生的。它是一套围绕OpenAPI规范构建的开源工具框架,可以帮助你 设计、构建、记录和使用 RESTful API

简单来说,Swagger的核心价值在于:

  • 自动生成交互式文档:只需要在代码中添加一些注解,Swagger就能自动生成一份美观、详细且可交互的API文档。前端开发者可以直接在文档页面上查看API详情并进行调用测试。
  • 保证文档与代码同步:文档是根据代码生成的,所以只要代码更新了,文档就会自动更新,彻底解决了文档与代码不一致的问题。
  • 标准化API定义:它提供了一套标准的、与语言无关的规范来描述API,使得计算机和人都能轻松理解API的功能和期望。
  • 简化团队协作:通过这份标准的、可交互的文档,前端、后端和测试人员可以高效地协作,减少沟通障碍。
  • 多语言支持:支持多种编程语言和框架

简单来说,Swagger就是让你的API文档"活"起来,永远与代码保持一致,并且可以直接在浏览器中测试。

二、Swagger2和Swagger3区别

我们通常说的Swagger 3,实际上是指实现了 OpenAPI 3.0 (OAS 3) 规范的工具。而Swagger 2对应的是旧的 Swagger 2.0 规范。OAS 3是一个重大的升级,带来了许多改进。

在Spring Boot生态中,最直观的体现是依赖库的变化:

  • Swagger 2 : 主要使用 SpringFox 库。
  • Swagger 3 (OAS 3) : 主流使用 SpringDoc 库,它与Spring Boot 3.x及更高版本兼容性更好,配置也更简单。

主要区别对比:

特性 Swagger 2 (SpringFox) Swagger 3 / OpenAPI 3 (SpringDoc) 优势说明
规范 Swagger 2.0 OpenAPI 3.0 OAS 3是当前行业标准,功能更强大。
依赖库 springfox-swagger2 springdoc-openapi-starter-webmvc-ui SpringDoc 对新版Spring Boot支持更好,配置更自动化。
组件化 定义有限,复用性一般 引入 components 对象,可轻松复用Schemas, Parameters, Responses等 结构更清晰,提高了规范的可维护性。
服务器定义 只能定义单个 hostbasePath 可定义多个 servers,每个server可以有自己的URL和描述 更灵活,轻松应对开发、测试、生产等多个环境。
请求体 使用 in: body 的参数来描述 引入独立的 requestBody 对象,可以描述更复杂的请求体和多种媒体类型 对请求体的描述更精确、更强大。
安全性 securityDefinitions securitySchemes 增强了对OAuth2和OpenID Connect的支持,描述更灵活。

结论: 如果你正在开始一个新项目,或者你的项目使用的是Spring Boot 2.x.x后期版本或3.x.x,强烈建议直接使用基于OpenAPI 3的SpringDoc。

三、集成 Swagger 3 (SpringDoc)

我们以目前最主流的SpringDoc为例,介绍如何在Spring Boot项目中集成。

1、如何集成?

集成过程非常简单,只需要一步:在你的pom.xml文件中添加依赖。

xml 复制代码
<!-- 访问 /swagger-ui.html 即可查看API文档 -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version> <!-- 建议使用最新稳定版 -->
</dependency>

2、最小配置是什么样的?

答案是:不需要任何Java配置!一副牛B哄哄的样子

是的,你没有看错。只要你将上面的依赖添加到项目中,并重新启动你的Spring Boot应用,SpringDoc就会自动开始工作。它会:

  • 扫描项目中所有被 @RestController 注解的类。
  • 解析被 @RequestMapping, @GetMapping, @PostMapping 等注解标记的方法,生成API信息。

启动成功后,在浏览器中访问 http://localhost:8080/swagger-ui.html (端口号替换成你自己的),你就可以看到自动生成的API文档了。

访问地址:

  • Swagger UI界面http://localhost:8080/swagger-ui.html
  • API文档JSONhttp://localhost:8080/v3/api-docs

四、配置 Swagger 3

虽然零配置就能运行,但在实际项目中,我们通常需要自定义一些信息,如作者、标题、版本等,让文档更清晰。springdoc-openapi 同时支持 YML 属性配置Java 配置类 两种方式。它们各有优势,并且可以同时使用,相辅相成。

配置全局信息(标题、作者等)

这些信息可以通过项目的配置文件 application.propertiesapplication.yml 来设置。这种方式简单直观,适用于大多数情况。

使用 application.yml (推荐):

yml 复制代码
springdoc:
  api-docs:
    info:
      title: '我的项目API文档' # API文档标题,显示在Swagger UI顶部
      description: '这是一个使用Spring Boot和Swagger 3构建的示例项目。' # API详细描述,支持Markdown格式
      version: 'v1.0.0' # API版本号
      termsOfService: 'http://swagger.io/terms/'  # 服务条款URL,用户需要遵守的条款
      
      # 联系信息
      contact: 
        name: '洛小豆' # 负责人或团队名称
        email: 'luoxiaodou@juejin.com' # 作者邮箱
        url: 'https://juejin.cn/user/2049145406229127' # 联系网址或个人网站
      
      # 许可证信息
      license:
        name: 'Apache 2.0' # 使用的许可证名称(如:Apache 2.0, MIT等)
        url: 'https://www.apache.org/licenses/LICENSE-2.0.html' # 许可证详细内容的URL

使用 Java 配置类

如果希望通过编程方式来配置,可以使用一个 Java 配置类。这种方式提供了更大的灵活性,例如,你可以根据环境动态地设置不同的元数据。

你需要创建一个配置类,并使用 @Configuration@Bean 注解来定义 OpenAPI 对象。

java 复制代码
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
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;

/**
 * OpenAPI 配置类
 * 用于配置Swagger/OpenAPI文档生成
 */
public class OpenAPIConfig {

    /**
     * 创建自定义OpenAPI配置Bean
     * 该方法会返回一个配置好的OpenAPI实例,用于生成API文档
     *
     * @return OpenAPI 配置实例
     */
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            // 设置API基本信息
            .info(new Info()
                // API文档标题,显示在Swagger UI顶部
                .title("我的项目API文档")
                // API详细描述,支持Markdown格式
                .description("这是一个使用Spring Boot和Swagger 3构建的示例项目。")
                // API版本号
                .version("v1.0.0")
                // 服务条款URL
                .termsOfService("http://swagger.io/terms/")
                // 设置联系信息
                .contact(new Contact()
                    // 负责人或团队名称
                    .name("洛小豆")
                    // 个人主页或项目文档链接
                    .url("https://juejin.cn/user/2049145406229127")
                    // 联系邮箱
                    .email("luoxiaodou@juejin.com"))
                // 设置许可证信息
                .license(new License()
                    // 许可证名称
                    .name("Apache 2.0")
                    // 许可证详细内容URL
                    .url("http://www.apache.org/licenses/LICENSE-2.0.html"))
            );
    }
}

配置API扫描范围

SpringDoc 提供了多种方式来精确控制哪些API需要被展示在文档中。

YML 属性配置

可以通过 springdoc.packagesToScanspringdoc.pathsToMatch 属性来配置扫描范围。

  • packagesToScan: 指定需要扫描的 Java 包名。
  • pathsToMatch: 指定需要匹配的 URL 路径模式。
  • packagesToExclude: 定义排除的包
  • pathsToExclude: 定义排除的路径
yaml 复制代码
springdoc:
  # 扫描指定包下的控制器
  packagesToScan: cn.luoxiaodou.controller
  # 或者更精确地指定多个包
  # packagesToScan: com.yourcompany.api, com.yourcompany.service.controller
  
  # 匹配指定的 URL 路径
  pathsToMatch: /api/v1/**
  # 或者匹配多个路径
  # pathsToMatch: /public/**, /private/**
  
  # 定义排除的包和路径
  # 即使某个类在 packagesToScan 范围内,如果它在 com.example.controller.internal 包下,也会被排除
  packagesToExclude: cn.luoxiaodou.controller.internal
  # 排除所有以 /public/health 开头的路径
  pathsToExclude: /public/health/**

Java 配置类

需要更精细、更灵活的控制,比如为不同的模块或版本创建独立的 API 文档,或者需要根据业务逻辑动态生成配置,那么 Java 配置类是更好的选择。

java 复制代码
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

    // 全局配置:定义 API 的基本信息
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("我的应用 API 文档")
                        .description("这是一个通过 Java 配置类演示 springdoc-openapi3 的 API 文档。")
                        .version("v1.0.0"));
    }
    
    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("public-apis")  // 分组名称
                // pathsToMatch(String... pathsToMatch),这里可以输入多个值
                .pathsToMatch("/public/**") // 匹配所有以 /public/ 开头的路径
                .pathsToExclude("/public/health/**") // 排除健康检查路径
                .build();
    }
}

配置API的分组

当项目很大,API很多时,把所有API都放在一个页面上会显得很杂乱。分组功能可以让你根据模块(如用户模块、订单模块)来组织API。

这需要通过Java代码配置。创建一个配置类,并定义多个 GroupedOpenApi 类型的Bean。

java 复制代码
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi userApi() {
        return GroupedOpenApi.builder()
                .group("用户模块") // 分组名
                .pathsToMatch("/users/**") // 该组包含的API路径
                .build();
    }

    @Bean
    public GroupedOpenApi orderApi() {
        return GroupedOpenApi.builder()
                .group("订单模块")
                .packagesToScan("cn.luoxiaodou.project.controller.order") // 该组包含的包
                .build();
    }
    
    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("公共接口")
                .pathsToMatch("/**")
                .pathsToExclude("/users/**", "/orders/**") // 排除其他组已经包含的路径
                .build();
    }
}

配置完成后,Swagger UI页面的右上角会出现一个下拉框,你可以在不同的分组之间切换。

为什么不推荐使用 YML 配置进行分组?

虽然你可以通过 YML 配置来扫描和排除路径,但这只能实现单个、全局的配置,无法像 GroupedOpenApi 那样创建多个独立的、可切换的分组。如果你只在乎扫描范围,YML 足够了;但如果你的目标是更好地组织和呈现文档,Java 配置类是实现分组的唯一推荐方式。


多环境配置,启用/禁用Swagger

在使用 springdoc-openapi3 时,有时你需要根据不同的环境(例如开发、测试或生产环境)来启用或禁用 Swagger 文档。

YML 属性配置

这是最常用、最简单的配置方式。你可以通过设置 springdoc.swagger-ui.enabled 属性来控制 Swagger UI 的启用或禁用。

yaml 复制代码
# 在开发和测试环境中启用 Swagger UI
springdoc:
  swagger-ui:
    enabled: true
  api-docs:
    enabled: true

# 在生产环境中禁用 Swagger UI
springdoc:
  swagger-ui:
    enabled: false
  api-docs:
    enabled: false

通常,我们会将这个配置放在不同的配置文件中,例如 application-dev.yml(启用)和 application-prod.yml(禁用),这样 Spring Boot 会根据你的活动 profile 自动加载对应的配置。

Java 配置类

如果你需要更复杂的逻辑来决定是否启用 Swagger,比如根据某些自定义条件,那么可以使用 Java 配置类。

你可以创建一个配置类,并利用 @ConditionalOnProperty 注解来实现条件启用。

java 复制代码
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(name = "springdoc.swagger-ui.enabled", havingValue = "true", matchIfMissing = true)
public class SwaggerEnableConfig {

    // 这个配置类只有在 springdoc.swagger-ui.enabled=true 时才会被加载
    // matchIfMissing = true 意味着如果该属性不存在,则默认启用
}

实体类(Model)配置

Swagger会自动扫描你在API中用作参数(@RequestBody)或返回值(ResponseEntity)的Java实体类,并在文档的 "Schemas" 部分展示其结构。

我们可以通过注解来丰富实体类的信息。

  • @Schema: 这是最核心的注解,用于描述一个实体类或其字段。

    • description: 描述信息。
    • example: 示例值。
    • requiredMode: 字段是否必须。RequiredMode.REQUIRED (必须), RequiredMode.NOT_REQUIRED (非必须)。
    • defaultValue: 默认值。

@Schema注解详细使用代码

java 复制代码
@Schema(
    description = "用户详细信息",           // 描述
    example = "User object example",       // 示例说明
    title = "User",                        // 标题
    requiredProperties = {"username", "email"} // 必填字段
)
public class UserDetail {
    
    @Schema(
        description = "用户唯一标识",       // 字段描述
        example = "1001",                  // 示例值
        required = true,                   // 是否必填
        minimum = "1",                     // 最小值
        maximum = "999999"                 // 最大值
    )
    private Long id;
    
    @Schema(
        description = "用户名",
        example = "zhangsan",
        required = true,
        minLength = 3,                     // 最小长度
        maxLength = 20,                    // 最大长度
        pattern = "^[a-zA-Z0-9_]+$"        // 正则表达式
    )
    private String username;
    
    @Schema(
        description = "用户类型",
        example = "NORMAL",
        allowableValues = {"NORMAL", "VIP", "ADMIN"}  // 允许的值
    )
    private UserType userType;
    
    @Schema(description = "敏感信息", hidden = true)  // 隐藏字段
    private String password;
    
    @Schema(
        description = "用户标签",
        example = "["新用户", "活跃用户"]"
    )
    private List<String> tags;
}

验证注解结合使用

java 复制代码
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Schema(description = "用户注册请求")
public class UserRegisterRequest {
    
    @Schema(description = "用户名", example = "zhangsan")
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    private String username;
    
    @Schema(description = "密码", example = "123456")
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, message = "密码长度至少6位")
    private String password;
    
    @Schema(description = "邮箱", example = "zhangsan@example.com")
    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "邮箱不能为空")
    private String email;
    
    @Schema(description = "年龄", example = "25")
    @Min(value = 0, message = "年龄不能小于0")
    @Max(value = 150, message = "年龄不能大于150")
    private Integer age;
}

注意SpringDoc 会自动识别 jakarta.validation.constraints (或 javax.validation.constraints) 包下的注解,如 @NotBlank, @NotNull, @Size 等,并自动将字段标记为必填项和添加校验规则说明。

五、注解汇总

控制器类注解:

注解 作用 常用属性 示例
@Tag 给控制器添加标签和描述 name, description 标识API分组
@Hidden 隐藏整个控制器 - 不在文档中显示

使用示例:

java 复制代码
@Tag(name = "用户管理", description = "用户相关的增删改查操作")
@RestController
@RequestMapping("/api/user")
public class UserController {
    // 控制器方法
}

方法级注解

注解 作用 常用属性 示例
@Operation 描述API操作 summary, description, tags 方法说明
@Parameter 描述参数 name, description, required, example 参数说明
@Parameters 多个参数描述 value 包含多个@Parameter
@ApiResponse 描述响应 responseCode, description 响应说明
@ApiResponses 多个响应描述 value 包含多个@ApiResponse
@Hidden 隐藏方法 - 不在文档中显示

详细使用示例

java 复制代码
@Tag(name = "用户管理", description = "用户相关操作")
@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @Operation(
        summary = "获取用户列表",
        description = "分页获取系统中所有用户的信息,支持按条件筛选",
        tags = {"用户查询"}
    )
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "获取成功"),
        @ApiResponse(responseCode = "400", description = "请求参数错误"),
        @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping("/list")
    public PageResult<User> getUserList(
        @Parameter(description = "页码", example = "1", required = true)
        @RequestParam(defaultValue = "1") Integer page,
        
        @Parameter(description = "每页大小", example = "10", required = true)
        @RequestParam(defaultValue = "10") Integer size,
        
        @Parameter(description = "用户名搜索关键词", example = "zhang")
        @RequestParam(required = false) String keyword
    ) {
        // 业务逻辑
        return userService.findUsers(page, size, keyword);
    }
    
    @Operation(summary = "创建用户", description = "创建新的用户账户")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "创建成功"),
        @ApiResponse(responseCode = "400", description = "用户信息验证失败"),
        @ApiResponse(responseCode = "409", description = "用户名或邮箱已存在")
    })
    @PostMapping("/create")
    public User createUser(
        @Parameter(description = "用户信息", required = true)
        @Valid @RequestBody UserCreateRequest request
    ) {
        return userService.createUser(request);
    }
    
    @Operation(summary = "更新用户信息")
    @PutMapping("/{id}")
    public User updateUser(
        @Parameter(description = "用户ID", example = "1001", required = true)
        @PathVariable Long id,
        
        @Parameter(description = "更新的用户信息", required = true)
        @Valid @RequestBody UserUpdateRequest request
    ) {
        return userService.updateUser(id, request);
    }
    
    @Operation(summary = "删除用户")
    @DeleteMapping("/{id}")
    public void deleteUser(
        @Parameter(description = "要删除的用户ID", example = "1001", required = true)
        @PathVariable Long id
    ) {
        userService.deleteUser(id);
    }
    
    @Hidden  // 隐藏此接口,不在文档中显示
    @GetMapping("/internal")
    public String internalApi() {
        return "内部接口";
    }
}

实体类注解(重要):

注解 作用 常用属性 示例
@Schema 描述实体类或字段 description, example, required 实体说明
@JsonIgnore 忽略字段 - 隐藏敏感信息
@JsonProperty 自定义字段名 value 字段重命名
java 复制代码
// 请求实体
@Schema(description = "用户创建请求")
public class UserCreateRequest {
    
    @Schema(description = "用户名", example = "zhangsan", required = true)
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @Schema(description = "邮箱", example = "zhangsan@example.com", required = true)
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Schema(description = "年龄", example = "25")
    @Min(0) @Max(150)
    private Integer age;
}

// 响应实体
@Schema(description = "分页结果")
public class PageResult<T> {
    
    @Schema(description = "数据列表")
    private List<T> data;
    
    @Schema(description = "总记录数", example = "100")
    private Long total;
    
    @Schema(description = "当前页码", example = "1")
    private Integer page;
    
    @Schema(description = "每页大小", example = "10")
    private Integer size;
}

wagger2兼容注解

Swagger3仍然支持Swagger2的注解,但建议使用新的OpenAPI 3注解:

Swagger2注解 Swagger3替代注解 说明
@Api @Tag 控制器描述
@ApiOperation @Operation 方法描述
@ApiParam @Parameter 参数描述
@ApiModel @Schema 实体类描述
@ApiModelProperty @Schema 实体字段描述
@ApiResponse @ApiResponse 响应描述(相同)
@ApiIgnore @Hidden 隐藏接口

完整的最佳实践示例

java 复制代码
/**
 * 商品管理控制器
 */
@Tag(name = "商品管理", description = "商品的增删改查操作")
@RestController
@RequestMapping("/api/product")
@Validated
public class ProductController {
    
    @Autowired
    private ProductService productService;
    
    @Operation(
        summary = "获取商品详情",
        description = "根据商品ID获取商品的详细信息,包括库存、价格等"
    )
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "获取成功"),
        @ApiResponse(responseCode = "404", description = "商品不存在"),
        @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    @GetMapping("/{id}")
    public Product getProductById(
        @Parameter(description = "商品ID", example = "1001", required = true)
        @PathVariable Long id
    ) {
        return productService.findById(id);
    }
    
    @Operation(
        summary = "搜索商品",
        description = "根据关键词、分类、价格区间等条件搜索商品"
    )
    @GetMapping("/search")
    public PageResult<Product> searchProducts(
        @Parameter(description = "搜索关键词", example = "手机")
        @RequestParam(required = false) String keyword,
        
        @Parameter(description = "商品分类ID", example = "1")
        @RequestParam(required = false) Long categoryId,
        
        @Parameter(description = "最低价格", example = "100.00")
        @RequestParam(required = false) BigDecimal minPrice,
        
        @Parameter(description = "最高价格", example = "5000.00")
        @RequestParam(required = false) BigDecimal maxPrice,
        
        @Parameter(description = "页码", example = "1")
        @RequestParam(defaultValue = "1") @Min(1) Integer page,
        
        @Parameter(description = "每页大小", example = "20")
        @RequestParam(defaultValue = "20") @Min(1) @Max(100) Integer size
    ) {
        return productService.searchProducts(keyword, categoryId, minPrice, maxPrice, page, size);
    }
    
    @Operation(
        summary = "创建商品",
        description = "创建新的商品信息,需要管理员权限"
    )
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "创建成功"),
        @ApiResponse(responseCode = "400", description = "商品信息验证失败"),
        @ApiResponse(responseCode = "403", description = "权限不足")
    })
    @PostMapping
    public Product createProduct(
        @Parameter(description = "商品信息", required = true)
        @Valid @RequestBody ProductCreateRequest request
    ) {
        return productService.createProduct(request);
    }
    
    @Operation(summary = "批量上传商品图片")
    @PostMapping("/{id}/images")
    public List<String> uploadProductImages(
        @Parameter(description = "商品ID", required = true)
        @PathVariable Long id,
        
        @Parameter(description = "商品图片文件", required = true)
        @RequestParam("files") MultipartFile[] files
    ) {
        return productService.uploadImages(id, files);
    }
    
    @Hidden  // 管理员内部接口,不对外显示
    @DeleteMapping("/batch")
    public void batchDeleteProducts(@RequestBody List<Long> ids) {
        productService.batchDelete(ids);
    }
}

/**
 * 商品信息实体类
 */
@Schema(description = "商品信息")
public class Product {
    
    @Schema(description = "商品ID", example = "1001")
    private Long id;
    
    @Schema(description = "商品名称", example = "iPhone 15 Pro", required = true)
    private String name;
    
    @Schema(description = "商品描述", example = "最新款iPhone,配备A17芯片")
    private String description;
    
    @Schema(description = "商品价格", example = "7999.00", required = true)
    private BigDecimal price;
    
    @Schema(description = "库存数量", example = "100")
    private Integer stock;
    
    @Schema(description = "商品分类ID", example = "1")
    private Long categoryId;
    
    @Schema(description = "商品分类名称", example = "智能手机")
    private String categoryName;
    
    @Schema(description = "商品图片URL列表")
    private List<String> imageUrls;
    
    @Schema(description = "商品状态", example = "ACTIVE", 
            allowableValues = {"ACTIVE", "INACTIVE", "SOLD_OUT"})
    private ProductStatus status;
    
    @Schema(description = "商品标签", example = "["热销", "新品"]")
    private List<String> tags;
    
    @Schema(description = "创建时间", example = "2023-12-01T10:30:00")
    private LocalDateTime createTime;
    
    @Schema(description = "更新时间", example = "2023-12-01T10:30:00")
    private LocalDateTime updateTime;
    
    @JsonIgnore  // 不在API文档中显示
    private String internalCode;
    
    // getter和setter方法省略...
}

/**
 * 商品创建请求
 */
@Schema(description = "商品创建请求")
public class ProductCreateRequest {
    
    @Schema(description = "商品名称", example = "iPhone 15 Pro", required = true)
    @NotBlank(message = "商品名称不能为空")
    @Size(max = 100, message = "商品名称长度不能超过100字符")
    private String name;
    
    @Schema(description = "商品描述", example = "最新款iPhone")
    @Size(max = 1000, message = "商品描述长度不能超过1000字符")
    private String description;
    
    @Schema(description = "商品价格", example = "7999.00", required = true)
    @NotNull(message = "商品价格不能为空")
    @DecimalMin(value = "0.01", message = "商品价格必须大于0")
    @Digits(integer = 10, fraction = 2, message = "价格格式不正确")
    private BigDecimal price;
    
    @Schema(description = "库存数量", example = "100", required = true)
    @NotNull(message = "库存数量不能为空")
    @Min(value = 0, message = "库存数量不能小于0")
    private Integer stock;
    
    @Schema(description = "商品分类ID", example = "1", required = true)
    @NotNull(message = "商品分类不能为空")
    private Long categoryId;
    
    @Schema(description = "商品标签", example = "["热销", "新品"]")
    private List<String> tags;
}

六、常见问题和解决方案

问题1:中文乱码

yml 复制代码
# application.yml
server:
  servlet:
    encoding:
      charset: UTF-8
      force: true

springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    # 解决中文显示问题
    config-url: /v3/api-docs/swagger-config

问题2:接口返回类型显示不正确

java 复制代码
// 错误方式:返回类型模糊
@GetMapping("/users")
public Object getUsers() { ... }

// 正确方式:明确返回类型
@GetMapping("/users")
public ApiResult<List<User>> getUsers() { ... }

// 或者使用@Operation注解明确指定
@Operation(summary = "获取用户列表")
@ApiResponse(responseCode = "200", 
             description = "成功", 
             content = @Content(schema = @Schema(implementation = User.class)))
@GetMapping("/users")
public Object getUsers() { ... }

问题3:枚举类型显示优化

java 复制代码
@Schema(description = "用户状态")
public enum UserStatus {
    
    @Schema(description = "正常")
    ACTIVE("1", "正常"),
    
    @Schema(description = "禁用")
    INACTIVE("0", "禁用"),
    
    @Schema(description = "锁定")
    LOCKED("2", "锁定");
    
    private String code;
    private String description;
    
    UserStatus(String code, String description) {
        this.code = code;
        this.description = description;
    }
    
    // getter方法...
}
相关推荐
北城以北888835 分钟前
Spring定时任务与Spring MVC拦截器
spring boot·spring·mvc
Victor35643 分钟前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易43 分钟前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧1 小时前
Range循环和切片
前端·后端·学习·golang
WizLC1 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Mr.朱鹏1 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka
Victor3561 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法1 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长2 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈2 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端