第一章:初识 Swagger
1.1 什么是 Swagger?
简单来说,Swagger 是一个接口文档生成工具。但它不是普通的文档工具,它能做到:
- 自动生成:根据你的代码自动生成接口文档
- 实时更新:代码改了,文档自动跟着改
- 在线测试:不用 Postman,直接在文档页面就能测试接口
这就像是给你的接口装了一面"魔镜",代码长什么样,文档就长什么样。
1.2 为什么要用 Swagger?
说实话,刚开始我也觉得多写注解挺麻烦的。但用了一段时间后发现:
省时间:以前写文档要半小时,现在加几个注解 5 分钟搞定。
不会忘:代码即文档,改代码的时候注解就在眼前,想忘都难。
前后端协作更顺畅:前端同学直接看文档就能测试,不用老是来问"这个字段什么意思"。
新人上手快:新同事来了,给他一个 Swagger 地址,所有接口一目了然。
1.3 搞清楚这些概念(重要!)
刚接触 Swagger 的时候,你肯定会被一堆名词搞晕:Swagger、SpringDoc、Springfox、Knife4j,还有各种不同的访问地址。别慌,我来帮你理清楚。
Swagger 是什么?
Swagger 其实是一个规范 ,全称叫 OpenAPI Specification(OAS)。就像 Java 有 Java 语言规范一样,Swagger 定义了"接口文档应该长什么样"。
所以 Swagger 本身不是一个具体的工具,而是一套标准。基于这套标准,不同的人开发了不同的实现工具。
SpringDoc vs Springfox:两个不同的实现
这两个都是在 Spring Boot 项目中实现 Swagger 规范的工具库,但它们是不同团队开发的:
Springfox(老一代):
- 最早在 Spring 中实现 Swagger 的库
- 支持 Swagger 2.0 规范
- 最后一个版本是 3.0.0(2020年发布)
- 之后就不再维护了,对 Spring Boot 3.x 支持不好
- Maven 坐标:
io.springfox:springfox-boot-starter
SpringDoc(新一代):
- 后起之秀,专门为了替代 Springfox
- 支持最新的 OpenAPI 3.0 规范
- 持续维护更新,对 Spring Boot 3.x 完美支持
- 配置更简单,开箱即用
- Maven 坐标:
org.springdoc:springdoc-openapi-ui
简单来说:
- 老项目可能还在用 Springfox
- 新项目都应该用 SpringDoc
- 它们不能同时用,选一个就行
访问地址的区别
这里最容易混淆。不同的工具,访问地址不一样:
1. SpringDoc 的默认地址:
http://localhost:8080/swagger-ui.html
或
http://localhost:8080/swagger-ui/index.html
这是 SpringDoc 自带的 UI 界面,样式是 Swagger 官方的那种蓝色主题。
2. Springfox 的默认地址:
http://localhost:8080/swagger-ui/index.html
或
http://localhost:8080/swagger-ui/
和 SpringDoc 差不多,但配置方式不同。
3. Knife4j 的地址(重点来了):
http://localhost:8080/doc.html
Knife4j 是什么?
Knife4j 是一个增强工具 ,它不是替代 SpringDoc 或 Springfox,而是给它们换了一套更好看的 UI 界面。
想象一下:
- SpringDoc/Springfox 负责生成接口文档的数据
- Knife4j 负责把这些数据展示得更漂亮、更好用
Knife4j 的优势:
- 界面更美观,中文支持更好
- 功能更强大(搜索、排序、离线文档等)
- 可以配合 SpringDoc 或 Springfox 使用
使用 Knife4j 的依赖:
xml
<!-- Knife4j + SpringDoc -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
或者
xml
<!-- Knife4j + Springfox (老版本) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
引入 Knife4j 后,访问 http://localhost:8080/doc.html 就能看到它提供的界面了。
同时 ,原来的 /swagger-ui.html 也还能访问,两个 UI 共存,用哪个都行。
总结一下
| 名称 | 作用 | 访问地址 | 推荐度 |
|---|---|---|---|
| Swagger/OpenAPI | 接口文档规范(标准) | - | 必须知道 |
| SpringDoc | Swagger 的 Spring Boot 实现(新) | /swagger-ui.html |
⭐⭐⭐⭐⭐ 强烈推荐 |
| Springfox | Swagger 的 Spring Boot 实现(老) | /swagger-ui/ |
⭐⭐ 老项目才用 |
| Knife4j | UI 增强工具(可选) | /doc.html |
⭐⭐⭐⭐ 推荐加上 |
我的建议:
- 新手入门 :先用 SpringDoc,访问
/swagger-ui.html,最简单 - 想要更好体验 :加上 Knife4j,访问
/doc.html,界面更友好 - 看别人的项目 :先看
pom.xml里用的是 SpringDoc 还是 Springfox,然后试试不同的访问地址
看到这里,你应该能明白:
- 访问
/swagger-ui.html是在用 SpringDoc 或 Springfox 自带的 UI - 访问
/doc.html是在用 Knife4j 提供的增强 UI - 它们显示的接口数据是一样的,只是界面不同
第二章:快速上手
2.1 环境准备
我们用 Spring Boot 来演示,这是最常见的场景。
Maven 依赖(以 Swagger 3.0 也就是 SpringDoc 为例):
xml
<!-- Swagger 3.0 (SpringDoc) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
如果你用的是老版本 Swagger 2.x (Springfox):
xml
<!-- Swagger 2.x (老版本) -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
小提示:现在推荐用 SpringDoc(Swagger 3.0),它对 Spring Boot 3.x 支持更好,配置也更简单。下面我会两个版本都讲。
2.2 第一个 Swagger 文档
使用 SpringDoc(推荐):
Spring Boot 项目引入依赖后,什么都不用配置,直接启动项目,访问:
http://localhost:8080/swagger-ui.html
就能看到你的接口文档了!是不是很简单?
使用 Springfox(老版本):
需要添加配置类:
java
@Configuration
@EnableOpenApi // Springfox 3.x 用这个
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(PathSelectors.any())
.build();
}
}
2.3 写你的第一个接口文档
来写个用户查询接口:
java
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关的增删改查接口") // SpringDoc
// @Api(tags = "用户管理") // Springfox 的写法
public class UserController {
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户", description = "传入用户ID,返回用户详细信息") // SpringDoc
// @ApiOperation("根据ID查询用户") // Springfox 的写法
public User getUserById(
@Parameter(description = "用户ID", required = true, example = "1001") // SpringDoc
// @ApiParam(value = "用户ID", required = true, example = "1001") // Springfox
@PathVariable Long id) {
User user = new User();
user.setId(id);
user.setUsername("张三");
user.setEmail("zhangsan@example.com");
return user;
}
}
实体类也要加注解:
java
@Schema(description = "用户实体") // SpringDoc
// @ApiModel("用户实体") // Springfox
public class User {
@Schema(description = "用户ID", example = "1001") // SpringDoc
// @ApiModelProperty(value = "用户ID", example = "1001") // Springfox
private Long id;
@Schema(description = "用户名", required = true, example = "张三")
// @ApiModelProperty(value = "用户名", required = true, example = "张三")
private String username;
@Schema(description = "邮箱", example = "zhangsan@example.com")
// @ApiModelProperty(value = "邮箱", example = "zhangsan@example.com")
private String email;
// getter/setter 省略
}
重启项目,刷新 Swagger 页面,你就能看到漂亮的文档了!
2.4 使用 Knife4j(推荐)
前面说了,Swagger 官方的 UI 界面比较简陋,功能也少。Knife4j 就是为了解决这个问题而生的。
Knife4j 和 Swagger 的关系
用一个类比来说明:
- Swagger/OpenAPI:标准规范(就像 HTML 标准)
- SpringDoc/Springfox:实现工具(生成符合标准的接口数据)
- Swagger UI:官方的展示界面(像浏览器显示 HTML)
- Knife4j:增强版的展示界面(像加了各种插件的浏览器)
关键点:
- Knife4j 不是替代 SpringDoc 或 Springfox 的
- Knife4j 是在 SpringDoc/Springfox 基础上提供更好的 UI 界面
- 它们是搭配使用的关系,不是二选一
Knife4j 的优势:
- 界面更现代化,中文支持好
- 支持接口搜索、排序
- 可以导出离线文档(Markdown、HTML、Word)
- 支持多主题切换
- 接口调试更方便(支持全局参数)
快速上手 Knife4j
第一步:引入依赖
如果你用的是 SpringDoc(推荐):
xml
<!-- Spring Boot 2.x + SpringDoc + Knife4j -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
如果你的项目是 Spring Boot 3.x:
xml
<!-- Spring Boot 3.x + SpringDoc + Knife4j -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
如果你用的是老的 Springfox:
xml
<!-- Springfox + Knife4j (老版本) -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
第二步:配置(可选)
Knife4j 大部分情况下零配置 就能用,但你也可以在 application.yml 中自定义:
yaml
knife4j:
# 是否启用 Knife4j 增强功能
enable: true
# 是否开启生产环境保护(生产环境建议开启)
production: false
# 基本信息
setting:
language: zh_cn # 中文界面
enable-swagger-models: true # 显示 Models
enable-footer: false # 去掉底部的 footer
enable-footer-custom: false
第三步:访问 Knife4j 界面
启动项目后,访问:
http://localhost:8080/doc.html
你会看到一个全新的界面!
Knife4j 界面功能介绍
打开 http://localhost:8080/doc.html 后,你会看到:
1. 左侧菜单:
- 主页:显示项目信息和配置
- 接口分组:可以切换不同的 API 分组
- 全局参数设置:设置公共的请求头(比如 Token)
2. 中间区域:
- 接口列表和详情
- 支持搜索框,快速查找接口
3. 右上角工具栏:
- 搜索:全局搜索接口
- 个性化设置:调整字体大小、主题颜色
- 离线文档:导出 Markdown、HTML 等格式
- 授权:设置全局 Token
实际使用示例
设置全局 Token:
很多接口需要登录才能访问,每次调试都输入 Token 很麻烦。Knife4j 可以设置全局参数:
- 点击右上角的 "Authorize" 按钮(或者点击左侧菜单的"全局参数设置")
- 输入你的 Token,比如:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - 点击保存
之后所有接口调试时都会自动带上这个 Token,不用每次手动输入。
调试接口:
- 在左侧找到你要测试的接口,点击展开
- 填写参数(如果设置了 example,会自动填充)
- 点击 "调试" 按钮
- 查看返回结果
导出离线文档:
- 点击右上角的 "离线文档" 图标
- 选择导出格式(Markdown、HTML、Word)
- 点击下载
这个功能对于需要交付文档给客户或者归档很有用。
Knife4j vs Swagger UI 对比
| 功能 | Swagger UI | Knife4j |
|---|---|---|
| 基本接口展示 | ✅ | ✅ |
| 接口调试 | ✅ | ✅ 更方便 |
| 全局搜索 | ❌ | ✅ |
| 全局参数 | ❌ | ✅ 支持全局 Token |
| 离线文档导出 | ❌ | ✅ 多种格式 |
| 中文界面 | ❌ | ✅ |
| 主题切换 | ❌ | ✅ |
| 请求记录 | ❌ | ✅ |
两个 UI 可以共存
引入 Knife4j 后,两个 UI 界面都可以访问:
- Swagger 官方 UI :
http://localhost:8080/swagger-ui.html - Knife4j UI :
http://localhost:8080/doc.html
它们显示的接口数据是一样的,只是界面不同。你可以根据喜好选择用哪个。
我的建议:
- 开发调试 :用 Knife4j 的
/doc.html,功能更强大 - 给前端看:也用 Knife4j,中文界面更友好
- 习惯 Swagger 官方 UI :那就继续用
/swagger-ui.html
注意事项
1. 版本匹配问题
Knife4j 的版本要和 SpringDoc/Springfox 的版本匹配:
| Spring Boot 版本 | 基础库 | Knife4j 版本 |
|---|---|---|
| 2.x | SpringDoc | knife4j-openapi3-spring-boot-starter:4.3.0 |
| 3.x | SpringDoc | knife4j-openapi3-jakarta-spring-boot-starter:4.3.0 |
| 2.x | Springfox | knife4j-spring-boot-starter:3.0.3 |
2. 生产环境记得关闭
和 Swagger UI 一样,生产环境不要开启文档访问:
yaml
knife4j:
production: true # 生产环境设为 true,会禁用文档访问
或者在配置类中:
java
@Configuration
@Profile({"dev", "test"}) // 仅在开发和测试环境生效
public class Knife4jConfig {
// 配置
}
3. 如果访问 /doc.html 报 404
检查几点:
- 确认依赖已经引入
- 确认项目已经重新编译启动
- 试试访问
/swagger-ui.html,看基础的 Swagger 是否正常 - 检查是否配置了 Spring Security 拦截了这个路径
完整示例
假设我们有一个用户管理的接口,用 Knife4j 调试的完整流程:
1. 代码(和之前一样,不用改):
java
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理")
public class UserController {
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户")
public Result<User> getUserById(@PathVariable Long id) {
// 业务逻辑
return Result.success(new User());
}
}
2. 访问文档:
打开 http://localhost:8080/doc.html
3. 设置全局 Token(如果需要):
点击右上角"Authorize",输入:
Bearer your-token-here
4. 调试接口:
- 展开"用户管理" → "根据ID查询用户"
- 输入参数
id = 1 - 点击"调试"
- 查看返回结果
5. 导出文档(如果需要):
点击右上角"离线文档" → 选择 Markdown → 下载
小结
Knife4j 和 Swagger 的关系:
- Swagger 定义标准,SpringDoc/Springfox 实现标准,Knife4j 提供更好的展示界面
- 它们是搭配关系,不是替代关系
推荐组合:
- SpringDoc(生成文档数据)+ Knife4j(展示界面)= 完美组合 ⭐⭐⭐⭐⭐
核心优势:
- 界面更美观,功能更强大
- 全局参数、搜索、导出一应俱全
- 零配置,开箱即用
试试 Knife4j,你会发现写接口文档变成了一件愉快的事情!
第三章:常用注解详解
前面快速上手的时候,我们用了一些注解,但可能你还不太清楚每个注解具体是干什么的,有哪些属性可以配置。这一章我们详细讲解。
注意:SpringDoc 和 Springfox 的注解是不一样的!下面我会同时列出两个版本的用法。
3.1 类级别注解
@Tag(SpringDoc)/ @Api(Springfox)
作用:用在 Controller 类上,给整个接口分组添加说明。
SpringDoc 的 @Tag:
java
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关的增删改查接口")
public class UserController {
// ...
}
常用属性:
name:分组名称(必填),会显示在文档的左侧菜单description:分组描述,鼠标悬停时显示
Springfox 的 @Api:
java
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理", description = "用户相关的增删改查接口")
public class UserController {
// ...
}
常用属性:
tags:分组名称(可以是数组)description:描述信息
多个标签:
一个 Controller 可以属于多个分组:
java
// SpringDoc
@Tag(name = "用户管理")
@Tag(name = "权限管理")
public class UserController { }
// Springfox
@Api(tags = {"用户管理", "权限管理"})
public class UserController { }
3.2 方法级别注解
@Operation(SpringDoc)/ @ApiOperation(Springfox)
作用:用在接口方法上,描述这个接口是干什么的。
SpringDoc 的 @Operation:
java
@GetMapping("/{id}")
@Operation(
summary = "根据ID查询用户",
description = "传入用户ID,返回用户的详细信息,包括用户名、邮箱、创建时间等",
tags = {"用户管理"} // 可选,覆盖类上的 @Tag
)
public User getUserById(@PathVariable Long id) {
// ...
}
常用属性:
summary:简短描述(必填),显示在接口列表description:详细描述,点开接口后显示tags:可以重新指定分组hidden:是否隐藏这个接口(true则不显示)
Springfox 的 @ApiOperation:
java
@GetMapping("/{id}")
@ApiOperation(
value = "根据ID查询用户",
notes = "传入用户ID,返回用户的详细信息",
tags = {"用户管理"}
)
public User getUserById(@PathVariable Long id) {
// ...
}
常用属性:
value:简短描述notes:详细描述tags:指定分组hidden:是否隐藏
3.3 参数注解
@Parameter(SpringDoc)/ @ApiParam(Springfox)
作用:用在方法参数上,描述参数的含义。
SpringDoc 的 @Parameter:
java
@GetMapping("/{id}")
public User getUserById(
@Parameter(
name = "id",
description = "用户ID",
required = true,
example = "1001",
schema = @Schema(type = "integer", format = "int64")
)
@PathVariable Long id) {
// ...
}
常用属性:
name:参数名(一般会自动识别,可以不写)description:参数说明required:是否必填example:示例值(很重要!)schema:数据类型定义hidden:是否隐藏该参数
更多示例:
java
// 查询参数
@GetMapping("/search")
public List<User> searchUsers(
@Parameter(description = "用户名(支持模糊查询)", example = "张三")
@RequestParam(required = false) String username,
@Parameter(description = "页码,从1开始", example = "1")
@RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "每页数量,最大100", example = "10")
@RequestParam(defaultValue = "10") Integer pageSize) {
// ...
}
// 路径参数
@DeleteMapping("/{id}")
public Result deleteUser(
@Parameter(description = "要删除的用户ID", required = true, example = "1001")
@PathVariable Long id) {
// ...
}
Springfox 的 @ApiParam:
java
@GetMapping("/{id}")
public User getUserById(
@ApiParam(
value = "用户ID",
required = true,
example = "1001"
)
@PathVariable Long id) {
// ...
}
常用属性:
value:参数说明required:是否必填example:示例值defaultValue:默认值allowableValues:允许的值(枚举)
3.4 实体类注解
@Schema(SpringDoc)/ @ApiModel + @ApiModelProperty(Springfox)
作用:用在实体类和实体类的字段上,描述数据模型。
SpringDoc 的 @Schema:
java
@Schema(description = "用户实体")
public class User {
@Schema(
description = "用户ID",
example = "1001",
required = true,
accessMode = Schema.AccessMode.READ_ONLY // 只读,不能修改
)
private Long id;
@Schema(
description = "用户名",
example = "zhangsan",
required = true,
minLength = 3,
maxLength = 20
)
private String username;
@Schema(
description = "密码",
example = "123456",
minLength = 6,
accessMode = Schema.AccessMode.WRITE_ONLY // 只写,不返回
)
private String password;
@Schema(
description = "邮箱",
example = "zhangsan@example.com",
pattern = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$"
)
private String email;
@Schema(
description = "年龄",
example = "25",
minimum = "0",
maximum = "150"
)
private Integer age;
@Schema(
description = "用户状态",
example = "ACTIVE",
allowableValues = {"ACTIVE", "DISABLED", "DELETED"}
)
private String status;
@Schema(description = "创建时间", example = "2024-01-01 12:00:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(hidden = true) // 隐藏该字段,不在文档中显示
private String internalField;
// getter/setter
}
常用属性:
description:字段描述example:示例值required:是否必填minLength/maxLength:字符串长度限制minimum/maximum:数值范围限制pattern:正则表达式验证allowableValues:允许的值(枚举)accessMode:访问模式(READ_ONLY、WRITE_ONLY、READ_WRITE)hidden:是否隐藏defaultValue:默认值
Springfox 的 @ApiModel + @ApiModelProperty:
java
@ApiModel(description = "用户实体")
public class User {
@ApiModelProperty(
value = "用户ID",
example = "1001",
required = true,
accessMode = ApiModelProperty.AccessMode.READ_ONLY
)
private Long id;
@ApiModelProperty(
value = "用户名",
example = "zhangsan",
required = true
)
private String username;
@ApiModelProperty(
value = "密码",
example = "123456",
accessMode = ApiModelProperty.AccessMode.WRITE_ONLY
)
private String password;
// getter/setter
}
DTO 实体示例:
创建用户的请求参数:
java
@Schema(description = "创建用户请求参数")
public class UserCreateDTO {
@Schema(description = "用户名", required = true, example = "zhangsan", minLength = 3, maxLength = 20)
@NotBlank(message = "用户名不能为空")
private String username;
@Schema(description = "密码", required = true, example = "123456", minLength = 6)
@NotBlank(message = "密码不能为空")
private String password;
@Schema(description = "邮箱", example = "zhangsan@example.com")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
@Min(value = 1, message = "年龄必须大于0")
@Max(value = 150, message = "年龄不能超过150")
private Integer age;
// getter/setter
}
注意 :@Schema 的验证属性(如 minLength、minimum)只是用于文档展示,不会真正执行验证 。真正的验证需要用 JSR-303 的注解(@NotBlank、@Email、@Min 等)。
3.5 响应注解
@ApiResponse / @ApiResponses
作用:描述接口的返回结果,特别是不同的 HTTP 状态码对应的返回内容。
SpringDoc 的用法:
java
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "查询成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = User.class)
)
),
@ApiResponse(
responseCode = "404",
description = "用户不存在",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)
)
),
@ApiResponse(
responseCode = "500",
description = "服务器错误"
)
})
public Result<User> getUserById(@PathVariable Long id) {
// ...
}
Springfox 的用法:
java
@ApiResponses({
@ApiResponse(code = 200, message = "查询成功", response = User.class),
@ApiResponse(code = 404, message = "用户不存在"),
@ApiResponse(code = 500, message = "服务器错误")
})
public Result<User> getUserById(@PathVariable Long id) {
// ...
}
统一返回格式的处理:
如果你的项目用了统一返回格式(如 Result<T>),可以这样写:
java
@ApiResponse(
responseCode = "200",
description = "成功",
content = @Content(schema = @Schema(implementation = UserResult.class))
)
public Result<User> getUser() {
// ...
}
// 为 Result<User> 创建一个具体的类
class UserResult extends Result<User> { }
3.6 请求体注解
@io.swagger.v3.oas.annotations.parameters.RequestBody(SpringDoc)
作用:描述 POST/PUT 请求的请求体。
注意 :这里用的是 Swagger 的 @RequestBody 注解,不是 Spring 的 @RequestBody!
java
@PostMapping
@Operation(summary = "创建用户")
public Result<User> createUser(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "用户信息",
required = true,
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = UserCreateDTO.class),
examples = {
@ExampleObject(
name = "示例1",
summary = "创建普通用户",
value = """
{
"username": "zhangsan",
"password": "123456",
"email": "zhangsan@example.com",
"age": 25
}
"""
),
@ExampleObject(
name = "示例2",
summary = "创建管理员",
value = """
{
"username": "admin",
"password": "admin123",
"email": "admin@example.com",
"role": "ADMIN"
}
"""
)
}
)
)
@org.springframework.web.bind.annotation.RequestBody UserCreateDTO userDTO) {
// ...
}
大部分情况下不用写:
如果你的 DTO 类已经用 @Schema 标注了字段,Spring 会自动识别,不需要额外写 @RequestBody 注解:
java
@PostMapping
@Operation(summary = "创建用户")
public Result<User> createUser(@RequestBody UserCreateDTO userDTO) {
// Spring 会自动根据 UserCreateDTO 里的 @Schema 注解生成文档
}
3.7 隐藏接口/参数
@Hidden(SpringDoc)/ @ApiIgnore(Springfox)
作用:隐藏不想在文档中显示的接口或参数。
隐藏整个接口:
java
@Hidden // SpringDoc
// @ApiIgnore // Springfox
@GetMapping("/internal/debug")
public String debugInfo() {
return "debug info";
}
隐藏整个 Controller:
java
@Hidden
@RestController
public class InternalController {
// 这个类的所有接口都不会出现在文档中
}
隐藏某个参数:
java
@GetMapping("/list")
public List<User> listUsers(
@Parameter(hidden = true) // 这个参数不会在文档中显示
@RequestParam(required = false) String internalParam,
@Parameter(description = "用户名")
@RequestParam(required = false) String username) {
// ...
}
隐藏实体类的某个字段:
java
public class User {
@Schema(hidden = true)
private String password; // 密码不在文档中显示
}
3.8 其他常用注解
@Content
作用:描述响应内容的格式和类型。
java
@ApiResponse(
responseCode = "200",
description = "成功",
content = {
@Content(
mediaType = "application/json",
schema = @Schema(implementation = User.class)
),
@Content(
mediaType = "application/xml",
schema = @Schema(implementation = User.class)
)
}
)
@ExampleObject
作用:提供请求或响应的示例数据。
java
@io.swagger.v3.oas.annotations.parameters.RequestBody(
content = @Content(
examples = @ExampleObject(
name = "创建用户示例",
value = """
{
"username": "zhangsan",
"email": "zhangsan@example.com"
}
"""
)
)
)
3.9 注解使用技巧
技巧1:善用 example
example 很重要!它能让前端一眼看懂参数格式,也能方便测试。
java
// 不好的例子
@Schema(description = "手机号")
private String phone;
// 好的例子
@Schema(description = "手机号", example = "13800138000")
private String phone;
// 更好的例子
@Schema(
description = "手机号",
example = "13800138000",
pattern = "^1[3-9]\\d{9}$"
)
private String phone;
技巧2:必填参数一定要标注
java
@Schema(description = "用户名", required = true, example = "zhangsan")
@NotBlank(message = "用户名不能为空") // 真正的验证
private String username;
技巧3:枚举类型要说明取值范围
java
@Schema(
description = "用户状态",
example = "ACTIVE",
allowableValues = {"ACTIVE", "DISABLED", "DELETED"}
)
private String status;
或者直接用枚举类:
java
@Schema(description = "用户状态", example = "ACTIVE")
private UserStatus status; // UserStatus 是枚举类
技巧4:敏感字段要隐藏
java
@Schema(description = "密码", accessMode = Schema.AccessMode.WRITE_ONLY)
private String password; // 只能写入,不会在响应中返回
技巧5:只读字段标注清楚
java
@Schema(description = "用户ID", accessMode = Schema.AccessMode.READ_ONLY)
private Long id; // 只读,创建/更新时不能传这个字段
3.10 SpringDoc vs Springfox 注解对照表
| 功能 | SpringDoc (推荐) | Springfox (老版本) |
|---|---|---|
| 类级别分组 | @Tag |
@Api |
| 方法级别描述 | @Operation |
@ApiOperation |
| 参数描述 | @Parameter |
@ApiParam |
| 实体类 | @Schema |
@ApiModel |
| 实体字段 | @Schema |
@ApiModelProperty |
| 响应描述 | @ApiResponse / @ApiResponses |
@ApiResponse / @ApiResponses |
| 请求体 | @io.swagger.v3.oas.annotations.parameters.RequestBody |
(不需要单独注解) |
| 隐藏 | @Hidden |
@ApiIgnore |
包路径:
- SpringDoc:
io.swagger.v3.oas.annotations.* - Springfox:
io.swagger.annotations.*
3.11 完整示例
来看一个综合运用各种注解的完整例子:
java
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关的增删改查接口")
public class UserController {
@PostMapping
@Operation(
summary = "创建用户",
description = "创建一个新用户,用户名必须唯一"
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "创建成功"),
@ApiResponse(responseCode = "400", description = "参数错误"),
@ApiResponse(responseCode = "409", description = "用户名已存在")
})
public Result<User> createUser(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "用户信息",
required = true
)
@RequestBody @Valid UserCreateDTO userDTO) {
// 业务逻辑
return Result.success(new User());
}
@GetMapping
@Operation(summary = "分页查询用户列表")
public Result<PageResult<User>> listUsers(
@Parameter(description = "用户名(模糊查询)", example = "张")
@RequestParam(required = false) String username,
@Parameter(description = "用户状态", example = "ACTIVE")
@RequestParam(required = false) UserStatus status,
@Parameter(description = "页码", example = "1")
@RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "每页数量", example = "10")
@RequestParam(defaultValue = "10") Integer pageSize) {
// 业务逻辑
return Result.success(new PageResult<>());
}
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "404", description = "用户不存在")
})
public Result<User> getUserById(
@Parameter(description = "用户ID", required = true, example = "1001")
@PathVariable Long id) {
// 业务逻辑
return Result.success(new User());
}
@PutMapping("/{id}")
@Operation(summary = "更新用户信息")
public Result<User> updateUser(
@Parameter(description = "用户ID", required = true, example = "1001")
@PathVariable Long id,
@RequestBody @Valid UserUpdateDTO userDTO) {
// 业务逻辑
return Result.success(new User());
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户")
public Result<Void> deleteUser(
@Parameter(description = "用户ID", required = true, example = "1001")
@PathVariable Long id) {
// 业务逻辑
return Result.success(null);
}
}
java
@Schema(description = "用户实体")
public class User {
@Schema(description = "用户ID", example = "1001", accessMode = Schema.AccessMode.READ_ONLY)
private Long id;
@Schema(description = "用户名", example = "zhangsan", required = true)
private String username;
@Schema(description = "邮箱", example = "zhangsan@example.com")
private String email;
@Schema(description = "年龄", example = "25", minimum = "0", maximum = "150")
private Integer age;
@Schema(description = "用户状态", example = "ACTIVE")
private UserStatus status;
@Schema(description = "创建时间", example = "2024-01-01 12:00:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "更新时间", example = "2024-01-01 12:00:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
// getter/setter
}
java
@Schema(description = "创建用户请求参数")
public class UserCreateDTO {
@Schema(description = "用户名", required = true, example = "zhangsan", minLength = 3, maxLength = 20)
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@Schema(description = "密码", required = true, example = "123456", minLength = 6)
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码长度不能少于6位")
private String password;
@Schema(description = "邮箱", example = "zhangsan@example.com")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
@Min(value = 1, message = "年龄必须大于0")
@Max(value = 150, message = "年龄不能超过150")
private Integer age;
// getter/setter
}
第四章:进阶配置
4.1 自定义文档信息
默认的文档标题和描述太简陋?来自定义一下:
SpringDoc 配置:
在 application.yml 中:
yaml
springdoc:
api-docs:
path: /api-docs # API 文档的路径
swagger-ui:
path: /swagger-ui.html # Swagger UI 的路径
tags-sorter: alpha # 标签按字母排序
operations-sorter: alpha # 接口按字母排序
或者写配置类:
java
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("我的项目 API 文档")
.version("1.0.0")
.description("这是一个用 Spring Boot 构建的 RESTful API")
.contact(new Contact()
.name("张三")
.email("zhangsan@example.com")
.url("https://github.com/zhangsan"))
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html")));
}
}
Springfox 配置:
java
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("我的项目 API 文档")
.description("这是一个用 Spring Boot 构建的 RESTful API")
.version("1.0.0")
.contact(new Contact("张三", "https://github.com/zhangsan", "zhangsan@example.com"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.build();
}
3.2 接口分组
项目大了以后,接口会很多。可以按模块分组:
SpringDoc:
java
@Configuration
public class SwaggerConfig {
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("用户模块")
.pathsToMatch("/api/users/**")
.build();
}
@Bean
public GroupedOpenApi orderApi() {
return GroupedOpenApi.builder()
.group("订单模块")
.pathsToMatch("/api/orders/**")
.build();
}
}
Springfox:
java
@Bean
public Docket userApi() {
return new Docket(DocumentationType.OAS_30)
.groupName("用户模块")
.select()
.paths(PathSelectors.ant("/api/users/**"))
.build();
}
@Bean
public Docket orderApi() {
return new Docket(DocumentationType.OAS_30)
.groupName("订单模块")
.select()
.paths(PathSelectors.ant("/api/orders/**"))
.build();
}
这样在 Swagger UI 右上角就能切换不同的模块了。
3.3 添加认证配置
很多接口需要登录才能访问,怎么在 Swagger 里加认证?
JWT Token 认证(SpringDoc):
java
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("bearer-jwt",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name("Authorization")))
.addSecurityItem(new SecurityRequirement().addList("bearer-jwt"));
}
配置好后,Swagger UI 右上角会出现一个 "Authorize" 按钮,点击输入 Token 就能测试需要认证的接口了。
Springfox 的写法:
java
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.securitySchemes(Collections.singletonList(securityScheme()))
.securityContexts(Collections.singletonList(securityContext()))
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.build();
}
private SecurityScheme securityScheme() {
return new ApiKey("Authorization", "Authorization", "header");
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any())
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Collections.singletonList(new SecurityReference("Authorization", authorizationScopes));
}
第四章:实战技巧
4.1 处理复杂请求参数
POST 请求示例:
java
@PostMapping
@Operation(summary = "创建用户", description = "创建一个新用户")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "创建成功"),
@ApiResponse(responseCode = "400", description = "参数错误"),
@ApiResponse(responseCode = "500", description = "服务器错误")
})
public Result<User> createUser(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "用户信息",
required = true,
content = @Content(schema = @Schema(implementation = UserCreateDTO.class)))
@RequestBody UserCreateDTO userDTO) {
// 业务逻辑
return Result.success(new User());
}
UserCreateDTO:
java
@Schema(description = "创建用户请求参数")
public class UserCreateDTO {
@Schema(description = "用户名", required = true, example = "zhangsan", minLength = 3, maxLength = 20)
@NotBlank(message = "用户名不能为空")
private String username;
@Schema(description = "密码", required = true, example = "123456", minLength = 6)
@NotBlank(message = "密码不能为空")
private String password;
@Schema(description = "邮箱", example = "zhangsan@example.com")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
private Integer age;
// getter/setter
}
4.2 枚举类型的优雅处理
枚举在接口文档中很容易让人困惑,要说清楚每个值的含义:
java
@Schema(description = "用户状态")
public enum UserStatus {
@Schema(description = "正常")
ACTIVE(1, "正常"),
@Schema(description = "禁用")
DISABLED(0, "禁用"),
@Schema(description = "已删除")
DELETED(-1, "已删除");
@Schema(description = "状态码")
private final Integer code;
@Schema(description = "状态描述")
private final String desc;
UserStatus(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
// getter
}
在实体类中使用:
java
@Schema(description = "用户状态", example = "ACTIVE",
allowableValues = {"ACTIVE", "DISABLED", "DELETED"})
private UserStatus status;
4.3 分页查询文档
分页是最常见的场景:
java
@GetMapping
@Operation(summary = "分页查询用户列表")
public PageResult<User> getUserList(
@Parameter(description = "页码", example = "1")
@RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "每页数量", example = "10")
@RequestParam(defaultValue = "10") Integer pageSize,
@Parameter(description = "用户名(模糊查询)", example = "张")
@RequestParam(required = false) String username,
@Parameter(description = "用户状态", example = "ACTIVE")
@RequestParam(required = false) UserStatus status) {
// 业务逻辑
return new PageResult<>();
}
分页结果封装:
java
@Schema(description = "分页结果")
public class PageResult<T> {
@Schema(description = "总记录数", example = "100")
private Long total;
@Schema(description = "当前页码", example = "1")
private Integer pageNum;
@Schema(description = "每页数量", example = "10")
private Integer pageSize;
@Schema(description = "数据列表")
private List<T> list;
// getter/setter
}
4.4 文件上传接口
java
@PostMapping("/upload")
@Operation(summary = "上传用户头像")
public Result<String> uploadAvatar(
@Parameter(description = "用户ID", required = true)
@RequestParam Long userId,
@Parameter(description = "头像文件", required = true,
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
@RequestParam("file") MultipartFile file) {
// 文件上传逻辑
return Result.success("http://example.com/avatar/123.jpg");
}
4.5 隐藏不想暴露的接口
有些内部接口不想在文档中显示:
java
@Hidden // SpringDoc
// @ApiIgnore // Springfox
@GetMapping("/internal/debug")
public String debugInfo() {
return "debug info";
}
或者整个 Controller 都隐藏:
java
@Hidden // SpringDoc
@RestController
public class InternalController {
// 这个类的所有接口都不会出现在文档中
}
第五章:常见问题与解决方案
5.1 Swagger UI 访问 404
问题 :启动项目后访问 /swagger-ui.html 返回 404。
解决方案:
-
检查依赖是否引入 :确认
pom.xml中有 Swagger 依赖。 -
检查路径:
- SpringDoc 默认路径是
/swagger-ui.html或/swagger-ui/index.html - Springfox 默认路径是
/swagger-ui/index.html或/swagger-ui/
- SpringDoc 默认路径是
-
如果用了 Spring Security,需要放行 Swagger 路径:
java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // SpringDoc
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs").permitAll() // Springfox
.anyRequest().authenticated();
}
}
5.2 接口文档不显示某些字段
问题:实体类有些字段在文档中不显示。
原因 :可能是这些字段没有 getter 方法,或者被 @JsonIgnore 标记了。
解决方案:
- 检查是否有 getter 方法
- 检查是否有
@JsonIgnore注解 - 如果确实不想序列化但想在文档中显示:
java
@JsonIgnore // JSON 序列化时忽略
@Schema(hidden = false) // 但在 Swagger 文档中显示
private String internalField;
5.3 日期格式问题
问题:日期字段在文档中显示不友好。
解决方案:
java
@Schema(description = "创建时间", example = "2024-01-01 12:00:00",
type = "string", format = "date-time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
或者全局配置:
yaml
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
5.4 泛型类型在文档中显示为 Object
问题 :Result<User> 在文档中显示为 Result<Object>。
解决方案:
java
@Operation(summary = "查询用户")
@ApiResponse(responseCode = "200", description = "成功",
content = @Content(schema = @Schema(implementation = UserResult.class)))
public Result<User> getUser(@PathVariable Long id) {
// ...
}
// 创建一个具体的返回类型
@Schema(description = "用户查询结果")
class UserResult extends Result<User> {
}
或者使用:
java
@Schema(implementation = User.class)
5.5 SpringBoot 3.x 兼容性问题
问题:Spring Boot 3.x 使用 Springfox 报错。
解决方案:Spring Boot 3.x 建议使用 SpringDoc,不再使用 Springfox。
xml
<!-- Spring Boot 3.x 推荐使用 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
第六章:最佳实践
6.1 注解书写规范
好的写法:
java
@Operation(
summary = "创建订单", // 简短描述,一句话说清楚
description = "用户下单接口,支持多商品下单,会进行库存校验和价格计算" // 详细说明
)
@Parameter(
name = "userId",
description = "用户ID",
required = true,
example = "10001" // 一定要给示例值
)
不好的写法:
java
@Operation(summary = "创建订单") // 太简单,看不懂干什么的
@Parameter(name = "userId") // 缺少必要信息
6.2 统一返回格式
建议使用统一的返回格式:
java
@Schema(description = "统一返回结果")
public class Result<T> {
@Schema(description = "状态码,200表示成功", example = "200")
private Integer code;
@Schema(description = "返回消息", example = "操作成功")
private String message;
@Schema(description = "返回数据")
private T data;
@Schema(description = "时间戳", example = "1640000000000")
private Long timestamp;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("操作成功");
result.setData(data);
result.setTimestamp(System.currentTimeMillis());
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
result.setTimestamp(System.currentTimeMillis());
return result;
}
// getter/setter
}
6.3 合理使用 example
example 很重要,它能让前端同学一眼看懂参数格式:
java
// 不好的例子
@Schema(description = "手机号")
private String phone;
// 好的例子
@Schema(description = "手机号", example = "13800138000", pattern = "^1[3-9]\\d{9}$")
private String phone;
6.4 环境隔离
生产环境不要开启 Swagger,会有安全风险:
方法一:配置文件控制
yaml
# application-prod.yml
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
方法二:代码控制
java
@Configuration
@Profile({"dev", "test"}) // 仅在开发和测试环境启用
public class SwaggerConfig {
// 配置
}
6.5 版本管理
接口升级后,可以通过版本号来管理:
java
@RestController
@RequestMapping("/api/v1/users")
@Tag(name = "用户管理 V1")
public class UserControllerV1 {
// V1 版本接口
}
@RestController
@RequestMapping("/api/v2/users")
@Tag(name = "用户管理 V2", description = "V2版本,新增了更多字段")
public class UserControllerV2 {
// V2 版本接口
}
第七章:实际项目案例
来看一个完整的电商项目接口示例:
7.1 商品查询接口
java
@RestController
@RequestMapping("/api/products")
@Tag(name = "商品管理", description = "商品的增删改查接口")
public class ProductController {
@GetMapping("/{id}")
@Operation(
summary = "查询商品详情",
description = "根据商品ID查询商品的详细信息,包括库存、价格、评价等"
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "404", description = "商品不存在",
content = @Content(schema = @Schema(implementation = Result.class)))
})
public Result<ProductDetailVO> getProductDetail(
@Parameter(description = "商品ID", required = true, example = "10001")
@PathVariable Long id) {
// 模拟业务逻辑
ProductDetailVO product = new ProductDetailVO();
product.setId(id);
product.setName("iPhone 15 Pro");
product.setPrice(new BigDecimal("7999.00"));
product.setStock(100);
product.setStatus(ProductStatus.ON_SALE);
return Result.success(product);
}
@GetMapping("/search")
@Operation(summary = "搜索商品", description = "支持按分类、价格区间、关键词搜索商品")
public Result<PageResult<ProductListVO>> searchProducts(
@Parameter(description = "关键词", example = "iPhone")
@RequestParam(required = false) String keyword,
@Parameter(description = "分类ID", example = "1")
@RequestParam(required = false) Long categoryId,
@Parameter(description = "最低价格", example = "1000")
@RequestParam(required = false) BigDecimal minPrice,
@Parameter(description = "最高价格", example = "10000")
@RequestParam(required = false) BigDecimal maxPrice,
@Parameter(description = "排序方式", example = "PRICE_ASC",
schema = @Schema(implementation = SortType.class))
@RequestParam(defaultValue = "CREATE_TIME_DESC") SortType sortType,
@Parameter(description = "页码", example = "1")
@RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "每页数量", example = "20")
@RequestParam(defaultValue = "20") Integer pageSize) {
// 业务逻辑
PageResult<ProductListVO> result = new PageResult<>();
result.setTotal(100L);
result.setPageNum(pageNum);
result.setPageSize(pageSize);
result.setList(new ArrayList<>());
return Result.success(result);
}
}
7.2 订单创建接口
java
@RestController
@RequestMapping("/api/orders")
@Tag(name = "订单管理", description = "订单相关接口")
public class OrderController {
@PostMapping
@Operation(summary = "创建订单", description = "用户下单接口,支持多商品购买")
@ApiResponse(responseCode = "200", description = "下单成功",
content = @Content(schema = @Schema(implementation = OrderResult.class)))
public Result<OrderVO> createOrder(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "订单信息",
required = true,
content = @Content(
schema = @Schema(implementation = CreateOrderDTO.class),
examples = @ExampleObject(
name = "订单示例",
value = """
{
"userId": 10001,
"items": [
{
"productId": 20001,
"quantity": 2
},
{
"productId": 20002,
"quantity": 1
}
],
"addressId": 30001,
"remark": "请在工作日配送"
}
"""
)
)
)
@RequestBody @Valid CreateOrderDTO orderDTO) {
// 业务逻辑
OrderVO order = new OrderVO();
order.setOrderId(System.currentTimeMillis());
order.setTotalAmount(new BigDecimal("15999.00"));
order.setStatus(OrderStatus.待付款);
return Result.success(order);
}
}
7.3 实体类定义
java
@Schema(description = "商品详情")
public class ProductDetailVO {
@Schema(description = "商品ID", example = "10001")
private Long id;
@Schema(description = "商品名称", example = "iPhone 15 Pro 256GB 黑色")
private String name;
@Schema(description = "商品价格", example = "7999.00")
private BigDecimal price;
@Schema(description = "库存数量", example = "100")
private Integer stock;
@Schema(description = "商品状态", example = "ON_SALE")
private ProductStatus status;
@Schema(description = "商品图片", example = "['http://example.com/img1.jpg', 'http://example.com/img2.jpg']")
private List<String> images;
@Schema(description = "商品描述")
private String description;
@Schema(description = "创建时间", example = "2024-01-01 12:00:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
// getter/setter 省略
}
@Schema(description = "创建订单请求")
public class CreateOrderDTO {
@Schema(description = "用户ID", required = true, example = "10001")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(description = "订单商品列表", required = true)
@NotEmpty(message = "商品列表不能为空")
@Valid
private List<OrderItemDTO> items;
@Schema(description = "收货地址ID", required = true, example = "30001")
@NotNull(message = "收货地址不能为空")
private Long addressId;
@Schema(description = "订单备注", example = "请在工作日配送")
private String remark;
// getter/setter
}
@Schema(description = "订单商品项")
public class OrderItemDTO {
@Schema(description = "商品ID", required = true, example = "20001")
@NotNull(message = "商品ID不能为空")
private Long productId;
@Schema(description = "购买数量", required = true, example = "2", minimum = "1")
@Min(value = 1, message = "数量至少为1")
private Integer quantity;
// getter/setter
}
@Schema(description = "排序方式")
public enum SortType {
@Schema(description = "按创建时间倒序")
CREATE_TIME_DESC,
@Schema(description = "按价格升序")
PRICE_ASC,
@Schema(description = "按价格降序")
PRICE_DESC,
@Schema(description = "按销量降序")
SALES_DESC
}
第八章:总结与建议
8.1 我的使用心得
用了这么久 Swagger,总结几点经验:
1. 养成好习惯:写接口的时候顺手加上注解,别等到最后再补,那时候你会忘记很多细节。
2. 注解要详细 :@Operation 的 description 多写几句话不会死,但能让看文档的人少问你几个问题。
3. example 很重要:每个字段都写上示例值,前端联调的时候会感谢你的。
4. 生产环境记得关:别让你的接口文档暴露在公网上,这是安全事故。
5. 定期检查文档:代码改了,文档注解也要跟着改,别让文档和实际不符。
8.2 SpringDoc vs Springfox
如果你现在要选,我建议:
- 新项目:直接用 SpringDoc,配置简单,支持更好
- 老项目:Spring Boot 2.x 可以继续用 Springfox,Spring Boot 3.x 建议迁移到 SpringDoc
- 学习目的:两个都了解一下,因为你遇到的老项目可能还在用 Springfox
8.3 不是所有接口都要写文档
有些内部接口、调试接口不需要暴露出来,用 @Hidden 隐藏即可。文档要精简,太多无用接口反而让人找不到重点。
8.4 文档只是辅助
虽然 Swagger 很方便,但它不能代替:
- 代码注释:复杂的业务逻辑还是要写清楚
- 沟通:前后端有疑问还是要沟通,别完全依赖文档
- 测试:文档能测试接口,但不能保证业务逻辑正确
8.5 扩展阅读
- OpenAPI 规范:https://swagger.io/specification/
- SpringDoc 官方文档:https://springdoc.org/
- Swagger Editor:https://editor.swagger.io/ (在线编辑 OpenAPI 文档)