Swagger2是一套基于OpenAPI规范(原Swagger规范)的开源工具集,专注于RESTful API的设计、构建、文档化和消费。它解决了传统API开发中的三大痛点:
生活化比喻:把Swagger2想象成餐厅的"智能菜单系统":
┌─────────────────────────────────────────────────┐
│ Swagger2生态体系 │
└───────────────┬─────────────────┬───────────────┘
│ │
┌───────────────▼─────┐ ┌─────────▼───────────────┐
│ Swagger Core │ │ Swagger UI │
│ (规范处理核心引擎) │ │ (可视化文档交互界面) │
└───────────────┬─────┘ └─────────┬───────────────┘
│ │
┌───────────────▼─────┐ ┌─────────▼───────────────┐
│ Springfox Core │ │ Springfox Swagger UI │
│ (Spring集成实现层) │ │ (UI的SpringBoot适配器) │
└─────────────────────┘ └─────────────────────────┘
对比维度 | Swagger2 | 传统文档(Word/Markdown) |
---|---|---|
实时性 | 代码变更自动同步 | 需要手动维护,容易过期 |
测试便利性 | 内置可视化测试工具 | 需要第三方工具 |
交互性 | 可交互式文档 | 静态文档 |
学习成本 | 需要学习注解体系 | 零技术门槛 |
适用阶段 | 开发阶段即可使用 | 开发完成后补充 |
团队协作 | 前后端基于同一份规范 | 容易产生理解偏差 |
使用Spring Initializr创建项目时勾选:
或手动添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 创建Docket Bean
* 比喻:就像配置相机的镜头,决定拍摄哪些画面
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 指定扫描的包路径(就像设置相机的对焦区域)
.apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
// 过滤路径(设置拍摄的景物范围)
.paths(PathSelectors.any())
.build()
// 添加全局参数(如给所有照片加滤镜)
.globalOperationParameters(globalParameters());
}
/**
* 配置API文档基本信息
* 比喻:就像相片的边框和标题说明
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("电商平台API文档") // 文档标题
.description("模拟淘宝部分功能的接口文档") // 详细描述
.version("1.0.0") // 版本号
.contact(new Contact("张三", "http://example.com", "[email protected]"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0")
.build();
}
/**
* 全局参数配置(如统一添加token参数)
* 比喻:给所有照片加上统一的水印
*/
private List<Parameter> globalParameters() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.description("访问令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build();
return Collections.singletonList(tokenBuilder.build());
}
}
关键配置解析表:
配置方法 | 作用说明 | 生活化类比 |
---|---|---|
.apis() |
指定扫描的控制器包路径 | 相机取景框的范围设置 |
.paths() |
过滤需要生成文档的接口路径 | 选择要拍摄的具体景物 |
.globalOperationParameters() |
添加全局参数(如认证头) | 给所有照片添加统一水印 |
apiInfo() |
设置文档的标题、描述等基本信息 | 相册的封面设计和说明文字 |
启动应用后访问:
http://localhost:8080/swagger-ui.html
UI界面区域解析:
头部区域:
API列表区:
接口详情卡:
参数区:
响应区:
@RestController
@RequestMapping("/api/products")
@Api(tags = "商品管理", description = "包含商品的CRUD操作")
public class ProductController {
@GetMapping("/{id}")
@ApiOperation(
value = "获取商品详情",
notes = "根据商品ID获取完整的商品信息,包括价格、库存等",
response = ProductVO.class
)
@ApiImplicitParams({
@ApiImplicitParam(
name = "id",
value = "商品唯一ID",
required = true,
dataType = "long",
paramType = "path",
example = "123"
),
@ApiImplicitParam(
name = "includeDetails",
value = "是否包含详情信息",
required = false,
dataType = "boolean",
paramType = "query",
defaultValue = "false"
)
})
@ApiResponses({
@ApiResponse(code = 200, message = "成功获取商品信息"),
@ApiResponse(code = 404, message = "商品不存在"),
@ApiResponse(code = 500, message = "服务器内部错误")
})
public ResponseEntity<ProductVO> getProductDetail(
@PathVariable Long id,
@RequestParam(required = false, defaultValue = "false") boolean includeDetails) {
// 业务逻辑实现
}
}
注解 | 属性 | 作用说明 | 示例值 |
---|---|---|---|
@Api |
tags | 控制器分类标签 | “用户管理” |
description | 控制器功能描述 | “包含用户的注册、登录等操作” | |
@ApiOperation |
value | 接口简要说明 | “创建新用户” |
notes | 接口详细说明 | “需要提供用户名、密码等基本信息” | |
response | 指定响应类型 | UserVO.class | |
@ApiImplicitParam |
name | 参数名称 | “username” |
value | 参数描述 | “用户名,4-20个字符” | |
required | 是否必填 | true | |
dataType | 参数数据类型 | “string” | |
paramType | 参数位置(path/query/body/header/form) | “query” | |
example | 参数示例值 | “zhangsan” | |
@ApiResponses |
- | 包装多个@ApiResponse | - |
@ApiResponse |
code | HTTP状态码 | 200 |
message | 状态描述 | “请求成功” |
@Data
@ApiModel(description = "商品数据传输对象")
public class ProductDTO {
@ApiModelProperty(
value = "商品唯一ID",
example = "123",
accessMode = AccessMode.READ_ONLY
)
private Long id;
@ApiModelProperty(
value = "商品名称",
example = "iPhone 13 Pro",
required = true,
notes = "长度限制2-100字符"
)
@NotBlank(message = "商品名称不能为空")
@Size(min = 2, max = 100)
private String name;
@ApiModelProperty(
value = "商品价格",
example = "6999.00",
dataType = "java.math.BigDecimal"
)
@DecimalMin(value = "0.01", message = "价格必须大于0")
private BigDecimal price;
@ApiModelProperty(
value = "库存数量",
example = "100",
allowableValues = "range[0, 10000]"
)
@Min(0)
@Max(10000)
private Integer stock;
@ApiModelProperty(
value = "上架状态",
example = "true",
hidden = true // 文档中隐藏该字段
)
private Boolean onSale;
@ApiModelProperty(
value = "商品分类",
example = "[\"电子产品\",\"手机\"]",
allowEmptyValue = true
)
private List<String> categories;
}
属性 | 作用说明 | 示例值 |
---|---|---|
value | 字段描述 | “用户名” |
example | 示例值 | “zhangsan” |
required | 是否必填 | true |
hidden | 是否隐藏字段 | false |
dataType | 覆盖默认数据类型 | “java.math.BigDecimal” |
allowableValues | 允许的值范围/列表 | “range[1, 100]” 或 “A,B,C” |
accessMode | 访问模式(READ_ONLY/READ_WRITE/AUTO) | AccessMode.READ_ONLY |
notes | 附加说明 | “长度需在2-20个字符之间” |
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "上传商品图片", consumes = "multipart/form-data")
@ApiImplicitParams({
@ApiImplicitParam(
name = "file",
value = "商品主图",
required = true,
dataType = "__file",
paramType = "form"
),
@ApiImplicitParam(
name = "productId",
value = "关联商品ID",
required = true,
dataType = "long",
paramType = "form"
)
})
public ResponseEntity<String> uploadProductImage(
@RequestPart MultipartFile file,
@RequestParam Long productId) {
// 文件处理逻辑
}
关键点:
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
dataType = "__file"
paramType
设为"form"@ApiModel(description = "订单状态枚举")
public enum OrderStatus {
@ApiModelProperty(value = "待支付")
PENDING_PAYMENT,
@ApiModelProperty(value = "已支付")
PAID,
@ApiModelProperty(value = "已发货")
SHIPPED,
@ApiModelProperty(value = "已完成")
COMPLETED,
@ApiModelProperty(value = "已取消")
CANCELLED
}
// 在DTO中使用
@ApiModelProperty(value = "订单状态")
private OrderStatus status;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 用户管理分组
*/
@Bean
public Docket userApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("用户管理")
.apiInfo(userApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.user"))
.paths(PathSelectors.ant("/api/user/**"))
.build()
.enable(true); // 可动态控制是否开启
}
/**
* 订单管理分组
*/
@Bean
public Docket orderApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("订单管理")
.apiInfo(orderApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.order"))
.paths(PathSelectors.regex("/api/order/.*"))
.build();
}
private ApiInfo userApiInfo() {
return new ApiInfoBuilder()
.title("用户管理API")
.description("用户注册、登录、权限管理等")
.version("1.0")
.build();
}
private ApiInfo orderApiInfo() {
return new ApiInfoBuilder()
.title("订单管理API")
.description("订单创建、支付、查询等")
.version("1.0")
.build();
}
}
分组方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
按业务模块 | 符合业务认知 | 需要合理划分包结构 | 中大型项目 |
按API版本 | 便于版本管理 | 需维护多版本代码 | 频繁迭代的项目 |
按访问权限 | 安全边界清晰 | 配置复杂 | 权限体系复杂的系统 |
按技术类型 | 技术关注点分离 | 业务人员理解成本高 | 技术异构系统 |
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
// ...其他配置
.securitySchemes(Collections.singletonList(apiKey()))
.securityContexts(Collections.singletonList(securityContext()));
}
private ApiKey apiKey() {
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");
return Collections.singletonList(
new SecurityReference("Authorization",
new AuthorizationScope[]{authorizationScope}));
}
效果:
Bearer your-jwt-token
@Bean
public SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()
.clientId("your-client-id")
.clientSecret("your-client-secret")
.realm("your-realm")
.appName("your-app-name")
.scopeSeparator(",")
.additionalQueryStringParams(null)
.useBasicAuthenticationWithAccessCodeGrant(false)
.build();
}
@Bean
@Profile({"dev", "test"}) // 只在开发测试环境启用
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(true) // 可基于配置动态控制
// ...其他配置
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/swagger-ui.html").hasRole("ADMIN")
.antMatchers("/v2/api-docs").authenticated()
// 其他配置...
}
}
问题现象 | 可能原因 | 解决方案 |
---|---|---|
404访问不到swagger-ui.html | 路径被拦截或版本冲突 | 1. 检查security配置 2. 确认依赖版本匹配 3. 尝试访问/v2/api-docs看JSON是否生成 |
模型属性未显示 | Lombok未生效或getter不规范 | 1. 安装Lombok插件 2. 检查@Data注解 3. 显式编写getter方法 |
文件上传参数不显示 | 未正确配置multipart参数 | 1. 确保consumes正确 2. 使用@ApiImplicitParam指定dataType=“__file” |
枚举类型显示为字符串 | 未配置枚举描述 | 1. 为枚举值添加@ApiModelProperty 2. 配置genericModelSubstitutes |
分组不生效 | 扫描路径冲突 | 1. 检查basePackage是否准确 2. 确认paths过滤条件不重叠 |
.apis(RequestHandlerSelectors.basePackage("com.your.package"))
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.enableCaching(true);
@Bean
@ConditionalOnProperty(name = "swagger.group.user.enabled", havingValue = "true")
public Docket userApi() {
// 用户分组配置
}
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理", description = "用户注册、登录和个人信息管理")
public class UserController {
@PostMapping("/register")
@ApiOperation(value = "用户注册", notes = "新用户注册接口")
@ApiResponses({
@ApiResponse(code = 201, message = "注册成功"),
@ApiResponse(code = 400, message = "参数校验失败"),
@ApiResponse(code = 409, message = "用户名已存在")
})
public ResponseEntity<Void> registerUser(
@Valid @RequestBody
@ApiParam(value = "用户注册信息", required = true) UserRegisterDTO registerDTO) {
// 注册逻辑
}
@PostMapping("/login")
@ApiOperation(value = "用户登录", notes = "用户名密码登录接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", required = true),
@ApiImplicitParam(name = "password", value = "密码", required = true)
})
public ResponseEntity<LoginResultVO> login(
@RequestParam String username,
@RequestParam String password) {
// 登录逻辑
}
}
@RestController
@RequestMapping("/api/orders")
@Api(tags = "订单管理", description = "订单创建、查询和状态管理")
public class OrderController {
@GetMapping
@ApiOperation(value = "分页查询订单", notes = "根据条件查询用户订单列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", value = "页码", defaultValue = "1"),
@ApiImplicitParam(name = "size", value = "每页数量", defaultValue = "10"),
@ApiImplicitParam(name = "status", value = "订单状态", allowableValues = "PENDING,PAID,COMPLETED,CANCELLED")
})
public Page<OrderVO> queryOrders(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) OrderStatus status) {
// 查询逻辑
}
@PostMapping("/{orderId}/cancel")
@ApiOperation(value = "取消订单", notes = "只有待支付的订单可以取消")
@ApiImplicitParam(name = "orderId", value = "订单ID", required = true, paramType = "path")
@ApiResponses({
@ApiResponse(code = 200, message = "取消成功"),
@ApiResponse(code = 400, message = "订单状态不允许取消"),
@ApiResponse(code = 404, message = "订单不存在")
})
public ResponseEntity<Void> cancelOrder(
@PathVariable Long orderId,
@RequestHeader("Authorization") String token) {
// 取消逻辑
}
}
命名一致性:
/users
代替/user
)版本控制:
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("v1.0")
.select()
.paths(PathSelectors.ant("/api/v1/**"))
// ...
@ApiOperation(value = "创建资源")
@ApiResponses({
@ApiResponse(code = 201, message = "创建成功", response = ResourceVO.class),
@ApiResponse(code = 400, message = "参数校验失败", response = ErrorResponse.class),
@ApiResponse(code = 401, message = "未授权", response = ErrorResponse.class)
})
开发阶段:
测试阶段:
维护阶段:
@ApiIgnore
标记)@Bean
public Docket actuatorApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("Actuator")
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Endpoint.class))
.paths(PathSelectors.any())
.build();
}
@Bean
public Swagger2MarkupConverter markupConverter() {
return Swagger2MarkupConverter.from("http://localhost:8080/v2/api-docs")
.withMarkupLanguage(MarkupLanguage.ASCIIDOC)
.build();
}
OpenAPI规范:
代码生成:
监控整合:
契约测试:
关注不关注,你自己决定(但正确的决定只有一个)。
喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!