SpringBoot 3 与 SpringDoc 打造完美接口文档

1. SpringDoc 简介

SpringDoc 是一个开源工具,它集成了 OpenAPI 3 和 Swagger UI,可以自动为基于 Spring Boot 开发的 REST API 生成 API 文档。SpringDoc 替代了过去的 SpringFox,并提供了与 SpringBoot 3 更好的兼容性。

1.1 SpringDoc 优势

  • 支持 OpenAPI 3 规范
  • 与 SpringBoot 3 完美集成
  • 自动扫描并生成 API 文档
  • 支持丰富的注解来定制 API 文档
  • 提供 Swagger UI 进行文档可视化
  • 支持分组、安全配置等高级特性

2. 环境准备

2.1 Maven 依赖

在 SpringBoot 3 项目中添加 SpringDoc 依赖:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>

对于 WebFlux 项目,使用:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>2.3.0</version>
</dependency>

2.2 基础配置

application.yml 中添加基础配置:

springdoc:
  api-docs:
    enabled: true                  # 启用/禁用API文档的访问
    path: /v3/api-docs            # 设置API文档的访问路径
  swagger-ui:
    path: /swagger-ui.html        # 设置Swagger UI的访问路径
    disable-swagger-default-url: true
    display-request-duration: true # 显示请求持续时间
  packages-to-scan: com.example.controller # 指定要扫描的包
  paths-to-match: /api/**, /public/** # 指定要匹配的路径

3. 创建基本文档配置类

创建一个配置类来自定义 API 文档:

package com.example.config;

import io.swagger.v3.oas.models.ExternalDocumentation;
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;

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("我的API文档")
                        .description("Spring Boot 3 应用接口文档")
                        .version("v1.0.0")
                        .contact(new Contact()
                                .name("开发者")
                                .email("developer@example.com")
                                .url("https://www.example.com"))
                        .license(new License()
                                .name("Apache 2.0")
                                .url("https://www.apache.org/licenses/LICENSE-2.0")))
                .externalDocs(new ExternalDocumentation()
                        .description("更多文档")
                        .url("https://springdoc.org"));
    }
}

4. 控制器 API 文档注解

4.1 基本控制器示例

package com.example.controller;

import com.example.model.User;
import com.example.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户管理相关接口")
public class UserController {

    @Autowired
    private UserService userService;

    @Operation(summary = "获取所有用户", description = "返回系统中所有用户的列表")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功获取用户列表"),
            @ApiResponse(responseCode = "401", description = "未授权"),
            @ApiResponse(responseCode = "403", description = "禁止访问"),
            @ApiResponse(responseCode = "500", description = "服务器错误")
    })
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.findAllUsers());
    }

    @Operation(summary = "通过ID获取用户", description = "根据ID查询并返回一个用户")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功找到用户",
                    content = @Content(schema = @Schema(implementation = User.class))),
            @ApiResponse(responseCode = "404", description = "用户不存在")
    })
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(
            @Parameter(description = "用户ID", required = true) @PathVariable Long id) {
        return ResponseEntity.ok(userService.findUserById(id));
    }

    @Operation(summary = "创建新用户", description = "创建一个新用户并返回")
    @PostMapping
    public ResponseEntity<User> createUser(
            @Parameter(description = "用户信息", required = true) @RequestBody User user) {
        return ResponseEntity.ok(userService.saveUser(user));
    }

    @Operation(summary = "更新用户", description = "更新指定ID的用户信息")
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(
            @Parameter(description = "用户ID", required = true) @PathVariable Long id,
            @Parameter(description = "更新后的用户信息", required = true) @RequestBody User user) {
        return ResponseEntity.ok(userService.updateUser(id, user));
    }

    @Operation(summary = "删除用户", description = "根据ID删除用户")
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(
            @Parameter(description = "要删除的用户ID", required = true) @PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.ok().build();
    }
}

4.2 模型类示例

package com.example.model;

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 User {

    @Schema(description = "用户ID", example = "1")
    private Long id;

    @Schema(description = "用户名", example = "zhangsan", required = true)
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20, message = "用户名长度必须在4-20之间")
    private String username;

    @Schema(description = "用户邮箱", example = "zhangsan@example.com")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Schema(description = "用户年龄", example = "25")
    private Integer age;
    
    // 构造函数、Getter和Setter方法省略
}

5. 高级功能

5.1 API分组

当你的API数量较多时,可以将它们分组展示:

package com.example.config;

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

@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("public-api")
                .pathsToMatch("/api/public/**")
                .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
                .group("admin-api")
                .pathsToMatch("/api/admin/**")
                .build();
    }

    @Bean
    public GroupedOpenApi userApi() {
        return GroupedOpenApi.builder()
                .group("user-api")
                .pathsToMatch("/api/users/**")
                .build();
    }
}

5.2 安全配置

为API添加安全配置(例如JWT令牌):

package com.example.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        final String securitySchemeName = "bearerAuth";
        return new OpenAPI()
                .info(new Info()
                        .title("安全API文档")
                        .version("1.0.0"))
                .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
                .components(new Components()
                        .addSecuritySchemes(securitySchemeName,
                                new SecurityScheme()
                                        .name(securitySchemeName)
                                        .type(SecurityScheme.Type.HTTP)
                                        .scheme("bearer")
                                        .bearerFormat("JWT")
                                        .description("请输入JWT令牌")));
    }
}

在控制器中使用:

@RestController
@RequestMapping("/api/secured")
@SecurityRequirement(name = "bearerAuth")
@Tag(name = "安全接口", description = "需要认证才能访问的接口")
public class SecuredController {
    
    @Operation(summary = "受保护的接口", description = "这个接口需要JWT认证")
    @GetMapping("/data")
    public ResponseEntity<String> getSecuredData() {
        return ResponseEntity.ok("这是受保护的数据");
    }
}

5.3 隐藏特定端点

有时你可能不想在文档中显示某些端点:

@Hidden // 隐藏整个控制器
@RestController
@RequestMapping("/api/internal")
public class InternalController {
    
    @GetMapping("/status")
    public String getStatus() {
        return "OK";
    }
}

或者隐藏特定方法:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Operation(summary = "公开接口")
    @GetMapping("/public")
    public String publicEndpoint() {
        return "这是公开的";
    }
    
    @Hidden // 隐藏此方法
    @GetMapping("/internal")
    public String internalEndpoint() {
        return "这是内部接口";
    }
}

6. 参数描述

6.1 路径参数

@GetMapping("/{id}")
public ResponseEntity<User> getUserById(
        @Parameter(description = "用户ID", example = "1", required = true)
        @PathVariable Long id) {
    // 实现代码
}

6.2 查询参数

@GetMapping
public ResponseEntity<List<User>> searchUsers(
        @Parameter(description = "用户名(支持模糊查询)", example = "zhang")
        @RequestParam(required = false) String username,
        
        @Parameter(description = "用户年龄(精确匹配)", example = "25")
        @RequestParam(required = false) Integer age,
        
        @Parameter(description = "排序字段", example = "username", schema = @Schema(allowableValues = {"id", "username", "age"}))
        @RequestParam(defaultValue = "id") String sortBy) {
    // 实现代码
}

6.3 请求体

@PostMapping
public ResponseEntity<User> createUser(
        @Parameter(description = "用户信息", required = true)
        @RequestBody User user) {
    // 实现代码
}

7. 响应文档化

7.1 基本响应

@Operation(summary = "获取用户")
@ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "成功获取用户"),
        @ApiResponse(responseCode = "404", description = "用户不存在"),
        @ApiResponse(responseCode = "500", description = "服务器错误")
})
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    // 实现代码
}

7.2 详细响应内容

@Operation(summary = "获取用户")
@ApiResponses(value = {
        @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 = "服务器错误",
                content = @Content(mediaType = "application/json",
                        schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    // 实现代码
}

7.3 自定义响应模型

@Schema(description = "错误响应")
public class ErrorResponse {
    
    @Schema(description = "HTTP状态码", example = "404")
    private int status;
    
    @Schema(description = "错误消息", example = "用户不存在")
    private String message;
    
    @Schema(description = "错误发生时间", example = "2023-08-15T14:30:15.123Z")
    private String timestamp;
    
    // 构造函数、Getter和Setter方法省略
}

8. 访问文档

配置好后,可以通过以下URL访问接口文档:

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

9. 常见问题及最佳实践

9.1 常见问题

  1. 文档未显示:检查包扫描配置是否正确,控制器是否有相应注解。

  2. 自定义模型未显示:确保模型类使用了@Schema注解,并且在控制器中引用。

  3. 日期格式不正确:配置全局日期格式转换器。

9.2 最佳实践

  1. 保持文档更新:确保代码变更时同步更新文档注解。

  2. 使用分组:当API数量增多时,适当分组以提高文档可读性。

  3. 添加足够的描述:为每个接口、参数和响应添加清晰的描述。

  4. 使用示例值:为参数和响应字段提供示例值,帮助调用者理解。

  5. 版本控制:明确标注API版本信息。

  6. 安全考虑:在生产环境中,考虑是否需要限制文档访问或完全禁用。

10. 完整示例

下面是一个完整的SpringBoot 3+SpringDoc项目结构示例:

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           ├── Application.java
│   │           ├── config/
│   │           │   └── OpenApiConfig.java
│   │           ├── controller/
│   │           │   ├── UserController.java
│   │           │   └── ProductController.java
│   │           ├── model/
│   │           │   ├── User.java
│   │           │   └── Product.java
│   │           └── service/
│   │               ├── UserService.java
│   │               └── ProductService.java
│   └── resources/
│       └── application.yml
└── test/

lication.java
│ │ ├── config/
│ │ │ └── OpenApiConfig.java
│ │ ├── controller/
│ │ │ ├── UserController.java
│ │ │ └── ProductController.java
│ │ ├── model/
│ │ │ ├── User.java
│ │ │ └── Product.java
│ │ └── service/
│ │ ├── UserService.java
│ │ └── ProductService.java
│ └── resources/
│ └── application.yml
└── test/


这个项目结构组织清晰,便于维护和扩展。通过合理使用SpringDoc的注解和配置,可以为API创建详细、易用的文档。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值