Spring Boot 集成 API 文档 - Swagger、Knife4J、Smart-Doc

发布时间:2024年01月21日


Swagger 作为 API 设计和文档的强大工具,是一个由专门的工具集合支持的框架,它在整个 API 的生命周期中发挥作用,从设计和文档,到测试和部署。通过提供可视化界面,Swagger 让开发人员和最终用户都能清晰地理解和操作 API。

使用建议:笔者建议优先考虑 Knife4J,它已经能够应付大多数应用场景,如果你考虑到代码的入侵,那么推荐尝试 smart-doc。不建议使用 Swagger,其对于高版本的 Spring Boot Bug 较多,也比较容易出现问题。

1.OpenAPI 规范

OpenAPI 规范(OAS),前身为 Swagger 规范,是一个强大的定义 RESTful API 属性的开放源规范。这个规范为 API 的路径、参数、响应、HTTP 方法等提供了一套详细的指南,从而标准化了 API 的描述方式。

利用 OpenAPI 规范,开发者可以确保API的结构和行为被准确且一致地描述,而不论它们是用什么编程语言或框架实现的。这个规范不仅有利于人类理解服务的功能,而且还使得软件可以自动识别和处理 API 的各个方面。

OpenAPI 规范的优势:

  1. 语言无关性:OpenAPI 规范是与编程语言无关的。这意味着无论应用程序是用 Python、Java、Node.js 或任何其他语言编写的,API 的定义都是通用的。
  2. 易于学习和阅读:由于 OpenAPI 规范具有高度的可读性,开发者和其他利益相关者可以快速理解 API 的功能和限制,无需深入代码或长时间的交流。
  3. 自动化支持:有了详尽的 API 定义,可以自动生成客户端库、服务器存根、文档以及其他实用的工具,从而加速开发过程并保持各种工具之间的同步。
  4. 促进 API 的可测试性:可以基于规范自动生成测试用例,从而提高 API 的质量和健壮性。
  5. 文档生成:与 Swagger UI 等工具集成时,可以动态生成 API 文档,让文档与代码实现保持一致,减少因手动编写而产生错误的风险。

官方 GitHub 地址 OpenAPI-Specification是获取规范的最佳起点,这里有规范的最新版本,以及如何开始和参与OpenAPI规范的详细说明。

2.Swagger: 接口管理的利器

Swagger 代表了一个前沿的解决方案,用于自动化生成和管理 RESTful API 文档。这个工具不仅仅是文档的生成器,它依据 OpenAPI(原称 Swagger 规范)提供了一套完整的规范,为描述和定义 API 端点、参数、响应等提供了标准化的手段。

Swagger 工具的核心功能体现在以下几个方面:

  1. 文档自动化生成:通过分析 API 代码或对 Swagger 规范的手动编写,Swagger 能够自动生成清晰的 API 文档。这些文档详细列出了每一个 API 端点的路径、使用的 HTTP 方法、所需参数以及响应示例。
  2. 交互式文档体验:Swagger 提供的文档不是静态的,而是交互式的。它们拥有友好的用户界面,使得用户可以轻松探索和理解 API 的功能及使用方法。更进一步,用户可以直接在文档界面中测试、调试甚至执行 API 请求。
  3. 客户端代码生成:Swagger 的一大亮点是其能够根据 API 文档生成客户端代码的能力,极大地方便了开发者在不同编程语言中的使用和集成工作。

在前后端分离的系统架构中,Swagger 解决了后端 API 文档与实际接口不同步的普遍问题。后端通过在 API 接口上添加 Swagger 注解,能够实现文档的自动生成和实时更新,确保了前后端在接口对接时的高效和准确。

Swagger 的实现和用途可以归纳为三点:

  1. 接口的可视化展示:后端开发者不需编写额外的接口文档,Swagger 会将所有对外的接口清晰展示在页面上。
  2. 实时的文档更新:随着接口的更新和维护,仅需修改代码中的 Swagger 描述,就能生成最新的接口文档,从而避免了因文档过时导致的接口使用误差。
  3. 减少调试成本:Swagger 页面不仅仅用于展示,还能直接发起接口调用,大大降低开发和调试阶段的成本和复杂度。

Swagger 通过这些功能,成为了开发者的有力工具,提高了开发效率,并促成了接口的标准化。

Swagger3 完全兼容 OpenAPI 规范。更多关于 Swagger 的信息,可以访问其官方网站:https://swagger.io/。

3.Swagger 与 SpringFox:理念与实现

Swagger 不仅仅是一个技术,它更是遵循 OpenAPI 规范的一整套解决方案。在这个生态圈中,SpringFox 是 Swagger 的一个重要实现,它如同是将 Swagger 的概念在 Spring 框架中具体化。若比喻说,Swagger 类似于控制反转(IOC)与依赖注入(DI)这些核心思想,那么 SpringFox 则是将这些思想落到实处的具体实践。

SpringFox 是一个流行的库,它使得将 Swagger 集成到基于 Spring 的 Java 应用中变得简单直接。它提供了自动配置和映射,允许开发者轻松定义 API 的元数据并生成相应的文档。这相当于是 Spring 框架中 IOC 和 DI 的实现,一个是理念的提出者,另一个则是将理念转化为可用工具的提供者。

4.Swagger 与 Knife4J:增强与创新

Knife4J,作为 Swagger 的增强解决方案,它的初衷是提供一种更加精简、灵活且功能强大的工具。其前身,swagger-bootstrap-ui,以其独特的方式结合了后端 Java 代码与前端 UI。随着微服务架构的流行和发展,此前的打包方式显得笨重,Knife4J 应运而生,更名并专注于提供更优的 Swagger 增强解决方案。

Knife4J 不仅仅对前端 UI 进行了改善,它还将前后端代码和 UI 模块进行了分离,为微服务环境下的使用提供了极大的便利。Knife4J 的目标是提供专注于 Swagger 的解决方案,而非仅仅停留在表面的 UI 改进上。

无论是 SpringFox 还是 Knife4J,它们都在 Swagger 的基础上做出了各自的特色和贡献,共同推动了 Swagger 生态的繁荣和发展。

想深入了解 Knife4J 增强解决方案的细节和文档,可以访问其官方文档:https://doc.xiaominfo.com/knife4j/documentation/

5.案例:Spring Boot 整合 Swagger3

5.1 引入 Swagger3 依赖包

集成 Swagger3 开始于添加必要的依赖。这些依赖负责引入 Swagger3 所需的所有功能,包括 API 文档自动化生成以及交互式界面。在项目的pom.xml文件中,你需要添加以下两个依赖项:

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>${springfox-swagger.version}</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${springfox-swagger-ui.version}</version>
</dependency>

注意:

  1. 上述以依赖的版本 ${springfox-swagger.version}${springfox-swagger-ui.version} 需要根据实际情况进行选择,以确保与项目的 Spring Boot 版本兼容。
  2. 目前 Swagger3 不支持 Spring Boot 3.0.x 及以上版本,因此这里的示例使用的是与 Spring Boot 2.7.11 兼容的版本。

5.2 优化路径匹配策略兼容 SpringFox

集成 Swagger3 到 Spring Boot 应用时,路径匹配策略的不一致可能会导致一系列问题。

  • Spring 采用默认的 PATH_PATTERN_PARSER 策略;
  • SpringFox 则采用了 ANT_PATH_MATCHER 策略。

这种差异可能会导致 Swagger3 文档中展示的端点路径和实际应用路径不匹配,进而影响文档的准确性和接口的可测试性。

为了确保路径匹配的一致性,并避免在使用 SpringFox 时出现问题,我们需要在 Spring 应用中手动配置路径匹配策略,以保证和 SpringFox 的策略对齐。

application.yml 中添加以下配置:

spring:
  mvc:
    pathmatch:
      # 统一配置路径匹配规则为Ant风格(与 Springfox 配置保持一致)
      matching-strategy: ant_path_matcher

这段配置的作用是将 Spring MVC 的路径匹配策略从默认的 PATH_PATTERN_PARSER 改为 ANT_PATH_MATCHER,从而保证与 SpringFox 使用的策略一致,确保 API 路径的匹配和文档的准确性。

通过这个简单的配置修改,我们就可以避免不稳定的情况发生,确保 SpringFox 能够无缝地与 Spring 应用集成,提供准确且一致的 API 文档。

5.3 配置 Swagger

Swagger 提供了丰富的配置选项,允许开发者根据项目的具体需求进行个性化设置。这些设置一般在 Spring Boot 项目的 application.yml 配置文件中指定。下面是一些最常用的 Swagger 配置项的介绍和如何进行配置。

application.yml 配置文件中,可以进行以下设置:

springfox:
  documentation:
    # 设置是否开启 Swagger 文档,默认为 true
    enabled: true
    # 设置是否开启 OpenApi 3.0+ 规范支持,默认为 true
    open-api:
      enabled: false
    # 设置是否启用 Swagger UI,默认为 true
    swagger-ui:
      enabled: true
      # 配置 Swagger UI 访问的基础路径,默认为 /swagger-ui
      # 这里我们自定义了基础路径为 /docs/swagger-ui
      base-url: /docs

详细配置说明:

  • enabled: 控制是否启用 Swagger 文档。在生产环境中,建议将此设置为 false 来禁用 API 文档。
  • open-api.enabled: 控制是否启用 OpenApi 3.0+ 的支持。根据你的项目需求,你可以选择开启或关闭它。
  • swagger-ui.enabled: 控制是否启用 Swagger UI,这是一个便于用户通过浏览器直接与 API 交互的界面。
  • swagger-ui.base-url: 自定义 Swagger UI 的访问路径。默认情况下,Swagger UI 可以通过 /swagger-ui/ 访问。这里我们将其修改为 /docs/swagger-ui,使得文档的路径更加符合常见的规范和直觉。

5.4 Swagger 配置类

对于 Spring Boot 项目来说,整合 Swagger 需要自定义配置类来启用和配置 Swagger。这一过程需要提供一个 Docket Bean,它是 Swagger-SpringMVC 插件的核心。

自定义的 Swagger 配置类关键代码如下:

import cn.edu.just.hostpital.system.common.ResponseStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Swagger 配置 (访问地址:http://localhost:8080/swagger-ui/index.html)
 * @author javgo.cn
 * @date 2024/1/20
 */
@Configuration
@EnableOpenApi // 启用 Swagger 3.0 版本的 API 文档生成功能
public class SwaggerConfig {

    // 定义 Docket Bean 来配置 Swagger 具体参数
    @Bean
    public Docket openApi() {
        return new Docket(DocumentationType.OAS_30)
                .groupName("开发组 API 文档") // API 文档分组名称, 用于区分多个分组(默认为 default)
                .apiInfo(apiInfo()) // 设置 API 文档的基本信息,这些信息会显示在文档页面上
                .select() // 配置扫描接口的路径和选择器
                .apis(RequestHandlerSelectors.basePackage("cn.edu.just.hostpital.system.controller")) // 指定 Swagger 扫描的包路径,仅生成这个包下面类的 API 文档
                .paths(PathSelectors.any()) // 指定路径选择器,这里是选择任何路径
                .build()
                .globalRequestParameters(getGlobalRequestParameters()) // 全局请求参数
                .globalResponses(HttpMethod.GET, getGlobalResponse()); // 全局响应配置
    }

    /**
     * 定义 API 文档的汇总信息,如文档标题、描述、联系人信息、版本号等
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger API") // 文档标题
                .description("DEV Swagger API 3.0") // 文档描述
                .contact(new Contact("javgo", "https://javgo.cn", "javgocn@gmail.com")) // 联系人信息
                .termsOfServiceUrl("https://javgo.cn") // 网站地址
                .version("1.0") // 版本号
                .build();
    }

    /**
     * 定义全局请求参数,全局请求参数将会添加到所有 API 接口中
     */
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder() // 参数配置
                .name("AppKey") // 参数名
                .description("AppKey") // 描述信息
                .in(ParameterType.QUERY) // 参数位置,在此为查询参数
                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))) // 设置参数的数据模型,这里是简单的字符串类型
                .required(false) // 是否必填
                .build());
        return parameters;
    }

    /**
     * 定义全局响应信息,对所有的 GET 请求,都会添加这里定义的响应状态码和描述
     */
    private List<Response> getGlobalResponse() {
        return ResponseStatus.HTTP_STATUS_ALL.stream() // 获取所有预定义的 HTTP 响应状态码及其描述
                .map(httpStatus -> new ResponseBuilder() // 响应配置
                        .code(httpStatus.getResponseCode()) // 响应状态码
                        .description(httpStatus.getDescription()) // 响应状态描述
                        .build())
                .collect(Collectors.toList());
    }
}

简单解释一下一些注意事项:

@EnableOpenApi 注解和 DocumentationType.OAS_30 选项是用于启用和指定使用 OpenAPI 3.0 规范的 Swagger 3.x 版本。而 @EnableSwagger2 注解和 DocumentationType.SWAGGER_2 是 Swagger 2.x 版本中用于启用和指定 Swagger 2.0 规范的选项。

两者之间的主要区别在于它们支持的 OpenAPI 规范版本不同。Swagger 2.x 版本是基于 OpenAPI 2.0 规范,而 Swagger 3.x(也被称为 Springfox 3.x)是基于更现代的 OpenAPI 3.0 规范。OpenAPI 3.0 带来了多项改进,包括但不限于:

  1. 更丰富的模式建模: OpenAPI 3.0 支持更复杂的数据结构,这使得 API 的描述可以更详细、更准确。
  2. 支持多服务器和环境: OpenAPI 3.0 允许指定多个服务器和不同环境,而 Swagger 2.0 仅支持单服务器。
  3. 改进的安全定义: OpenAPI 3.0 提供了更灵活的安全方案定义,允许在操作级别上应用安全方案。
  4. 新的链接功能: 提供了新的方式来描述 API 之间的关联和操作的顺序。
  5. 增强的参数复用: 通过使用 components 关键字,参数和模型的复用更加方便。

在选择使用哪一个版本的时候,通常建议根据项目需求和团队习惯来决定。如果你的项目要求或预期将需要使用 OpenAPI 3.0 规范的某些高级功能,或者希望保持与行业最佳实践的一致,那么推荐使用 Swagger 3.x 版本。如果你的项目还在使用旧版的 Swagger 或者出于兼容性的考虑需要继续使用 Swagger 2.0 规范,那么可以选择 Swagger 2.x 版本。

随着业界越来越多的工具和服务开始支持 OpenAPI 3.0 规范,并且考虑到 Swagger 3.x 版本本身也提供了向下兼容 Swagger 2.0 的方式,越来越多的新项目和迁移项目倾向于选择 Swagger 3.x 版本。因此,在大多数情况下,推荐使用 @EnableOpenApiDocumentationType.OAS_30 来支持最新的 OpenAPI 3.0 规范。


Spring Boot 2.6.x 及以上版本与 Springfox 3.0.0 集成时的兼容性问题

值得注意的是,Springfox 3.0.0 在与 Spring Boot 2.6.x 或更高版本集成时,存在一些已知的问题。原因是 Spring Boot 的某些内部行为改变了,这导致 Springfox 的一些预设条件不再适用。为了解决这个问题,特别是关于 RequestMappingHandlerMapping 的问题,需要添加一个额外的 BeanPostProcessor,它会在 Spring 容器初始化所有 bean 之后,对 Springfox 的处理器映射进行调整。

示例代码如下:

@Configuration
@EnableOpenApi
public class SwaggerConfig {
  
    // 省略其他代码 ... ...

    /**
     * BeanPostProcessor 用于修正 Spring Boot 2.6.x 及以上版本中的 Springfox 兼容性问题。
     * 这个问题源于 Springfox 的一些假设在 Spring Boot 新版本中不再成立。
     * 该 BeanPostProcessor 将调整 Springfox 的处理器映射,以确保正常工作。
     */
    @Bean
    @ConditionalOnClass(WebMvcRequestHandlerProvider.class)
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                // 检查 bean 是否为 Springfox 的请求处理器提供者
                if (bean instanceof WebMvcRequestHandlerProvider) {
                    // 通过反射获取并修改 HandlerMappings
                    List<RequestMappingInfoHandlerMapping> mappings = getHandlerMappings(bean);
                    customizeSpringfoxHandlerMappings(mappings);
                }
                return bean;
            }

            /**
             * 自定义 Springfox 的 HandlerMappings,移除任何没有处理器方法的映射,
             * 防止 Springfox 崩溃。
             *
             * @param mappings Springfox 的 HandlerMappings 列表
             */
            @SuppressWarnings("unchecked")
            private void customizeSpringfoxHandlerMappings(List<RequestMappingInfoHandlerMapping> mappings) {
                List<RequestMappingInfoHandlerMapping> validMappings = mappings.stream()
                        .filter(mapping -> mapping.getHandlerMethods().size() > 0)
                        .collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(validMappings);
            }

            /**
             * 通过反射获取指定 bean 的 handlerMappings 属性。
             *
             * @param bean Springfox 的请求处理器提供者 bean
             * @return HandlerMappings 列表
             */
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    ReflectionUtils.makeAccessible(field);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException("无法通过反射访问 handlerMappings", e);
                }
            }
        };
    }
}

上面我们通过添加一个 BeanPostProcessor 来解决 Spring Boot 2.6.x 以上版本中的 Springfox 兼容性问题。具体做法是:

  • 检查容器中的 bean 实例是否是 Springfox 的 WebMvcRequestHandlerProvider
  • 用反射获取其 handlerMappings 属性,该属性包含了 Spring MVC 的处理器映射。
  • 筛选这些映射,移除那些没有映射到任何处理器方法的映射。这样做是因为 Spring Boot 2.6.x 可能会导致 Springfox 生成的一些映射不包含任何处理器方法,进而导致应用启动失败。
  • 最终,只保留有效的处理器映射,从而保证 Springfox 能够正常工作。

这个过程对于保持 Springfox 与最新版本的 Spring Boot 兼容是必要的,可以有效地解决在升级 Spring Boot 后可能出现的 Swagger UI 不可用的问题。

5.5 Swagger 常用注解

Swagger 主要是通过注解来标注文档内容的,下面是一些常用注解:(会使用加粗的部分内容即可)

注解名描述常用属性
@Api用于类上,表示标记的类是 Swagger 的资源tags:用于控制 API 所属的标签列表,本质就是为 API 接口进行分组
@Tag用于类或方法上,声名一个标签name:标签名称
description:标签描述
@ApiOperation用于方法上,表示一个 HTTP 请求的操作value:API 操作名
notes:API 操作的描述
@ApiResponses用于方法上,表示一组 HTTP 响应@ApiResponse 数组
@ApiResponse用于 @ApiResponses 中,定义一个 HTTP 响应,描述一个可能的返回结果code:返回状态码
message:返回信息
response:返回对象
@ApiParam用于方法参数上,表示一个请求参数(推荐用下面这个注解)value:参数说明
defaultValue:默认参数值
name:参数名称
required:是否必须
allowableValues:参数允许范围
type:参数类型
@ApiImplicitParam用于方法上,声明每个请求参数的信息name:参数名称
value:参数说明
required:是否必须
dataType:数据类型,通过字符串 String 定义
dataTypeClass:数据类型(推荐)
paramType:参数所在位置的类型,有如下五种:
· path:对应 SpringMVC 的 @PathVariable 注解
· query:默认值,对应 SpringMVC 的 @PathVariable 注解(推荐)
· body:对应 SpringMVC 的 @RequestBody 注解
· header:对应 SpringMVC 的 @RequestHeader 注解
· form:Form 表单提交,对应 SpringMVC 的 @PathVariable 注解
example:参数值的简单示例
examples:参数值的复杂示例,使用 @Example 注解
@ApiIgnore用于类或方法上,表示该类或方法不在 Swagger UI 中显示value:添加备注
@ApiModel用于实体类,声明实体类的信息(在 Swagger 称为模型)value:模型名称
description:模型描述
@ApiModelProperty用于实体类的字段上,表示实体类属性,声明每个成员变量的信息value:字段说明
required:是否必须
example:属性值的简单示例
dataType:可以自动获得成员变量的类型(可忽略)

5.6 Controller

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付宝支付控制器
 *
 * @author javgo.cn
 * @date 2024/1/13
 */
@Api
@Controller
@RequestMapping("/alipay")
public class AlipayController {

    @Resource
    private AlipayConfig alipayConfig;

    @Resource
    private AlipayService alipayService;

    @ApiOperation("支付宝电脑网站支付")
    @GetMapping("/pcPayment")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "aliPayReq", type = "AliPayReq", value = "支付宝支付请求参数", required = true, dataTypeClass = AliPayReq.class),
            @ApiImplicitParam(name = "response", type = "HttpServletResponse", value = "响应", required = true, dataTypeClass = HttpServletResponse.class)
    })
    public void pcPayment(AliPayReq aliPayReq, HttpServletResponse response) throws IOException {
        response.setContentType(ContentType.TEXT_HTML.getContentType() + ";charset=" + alipayConfig.getCharset());
        Result<?> Result = alipayService.initiatePcPayment(aliPayReq);
        response.getWriter().write(Result.getData().toString());
        response.getWriter().flush();
        response.getWriter().close();
    }

    @ApiOperation("支付宝手机网站支付")
    @GetMapping("/mobilePayment")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "aliPayReq", type = "AliPayReq", value = "支付宝支付请求参数", required = true, dataTypeClass = AliPayReq.class),
            @ApiImplicitParam(name = "response", type = "HttpServletResponse", value = "响应", required = true, dataTypeClass = HttpServletResponse.class)
    })
    public void mobilePayment(AliPayReq aliPayReq, HttpServletResponse response) throws IOException {
        response.setContentType(ContentType.TEXT_HTML.getContentType() + ";charset=" + alipayConfig.getCharset());
        Result<?> Result = alipayService.initiateMobilePayment(aliPayReq);
        response.getWriter().write(Result.getData().toString());
        response.getWriter().flush();
        response.getWriter().close();
    }

    @ApiOperation("支付宝支付通知")
    @PostMapping("/notify")
    @ResponseBody
    @ApiImplicitParam(name = "request", type = "HttpServletRequest", value = "请求", required = true, dataTypeClass = HttpServletRequest.class)
    public Result<?> processPaymentNotification(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        requestParams.keySet().forEach(r -> params.put(r, request.getParameter(r)));
        return alipayService.processPaymentNotification(params);
    }

    @ApiOperation("查询支付状态")
    @GetMapping("/queryPaymentStatus")
    @ResponseBody
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", type = "String", value = "商户订单号", required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "tradeNo", type = "String", value = "支付宝交易号", required = true, dataTypeClass = String.class)
    })
    public Result<?> queryPaymentStatus(String outTradeNo, String tradeNo) {
        return alipayService.queryPaymentStatus(outTradeNo, tradeNo);
    }
}

5.7 Spring Security + Swagger(可选)

在集成了 Spring Security 的 Spring Boot 项目中,确保 Swagger 可以访问受保护的 API 接口是一个重要的步骤。Swagger 本身不处理身份验证,但它可以通过配置安全模式来与 Spring Security 集成,从而允许在 Swagger UI 中进行认证并访问受保护的端点。

为了解决这个问题,需要在 Swagger 的配置中添加 Security 方案,可以指定一个 “Authorization” header,所有请求都会携带这个 header,而 Spring Security 则可以基于这个 header 进行认证。

下面是 Swagger 相关配置:

@Configuration
@EnableOpenApi
public class SwaggerConfig {
    @Bean
    public Docket openApi() {
        return new Docket(DocumentationType.OAS_30)
                // ... ...
                // 添加安全模式,这里是一个名为 'Authorization' 的请求头
                .securitySchemes(List.of(new ApiKey("Authorization", "Authorization", "header")))
                // 定义需要使用安全模式的API上下文
                .securityContexts(Collections.singletonList(securityContext()));
    }

    /**
     * 创建 SecurityContext,定义哪些请求可以使用认证信息
     */
    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                // 这里我们将所有 API 都设置为需要使用认证信息
                .operationSelector(operationContext -> operationContext.requestMappingPattern().matches("/*"))
                .build();
    }

    /**
     * 设置默认的安全认证引用(全局认证)
     */
    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization", authorizationScopes));
        return result;
    }
}

在这个配置中,我们通过 securitySchemes 配置添加了一个类型为 ApiKey 的安全方案。这种安全方案定义了一个名为 Authorization 的 HTTP 头部,Swagger UI 将使用这个头部发送认证信息。然后,我们在 securityContext 中指定了安全方案应用于所有路径(通过 /* 正则表达式)。接着,使用 defaultAuth 方法定义了一个全局的认证范围。

通过这样的配置,Swagger UI 就可以在受 Spring Security 保护的环境中使用了,开发者可以在不离开 Swagger UI 的情况下测试所有 API 端点,包括那些需要认证的。

5.6 测试

在完成 Swagger 配置以及集成 Spring Security 之后,是时候进行测试以确保一切都按预期工作了。通过在浏览器中访问 Swagger UI,可以查看 API 文档并且测试 API 端点。

注意:你需要确保 Spring Security 允许对 Swagger UI 的静态资源进行匿名访问,并可能需要配置相应的安全规则来允许访问 Swagger 的端点。

下面是一个简单的 Spring Security 配置示例,用于允许对 Swagger UI 所需的静态资源进行匿名访问:

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 其他配置...
            .authorizeRequests()
                .antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // 允许匿名访问 Swagger UI
                // 其他路径的安全配置...
            // 其他配置...
    }
}

确保你的应用程序正在运行,然后在浏览器中打开 http://localhost:8080/swagger-ui/index.html。你应该能够看到 Swagger UI 的界面,并且如果之前步骤都正确,你会看到所有你的控制器和 API 端点的列表。从这里,你可以尝试发送请求以测试你的 API,并查看响应。

如果你的 API 端点需要认证,Swagger UI 应该提供一个地方让你输入必要的认证令牌或凭据。输入这些信息后,你应该能够测试那些受保护的端点就像测试任何其他不受保护的端点一样。

6.案例:Spring Boot 集成 Knife4J

Knife4J 是一款为 Spring Boot 应用提供更加友好的 Swagger 界面的工具,它提供了丰富的文档装饰特性,方便开发者生成、描述、调用甚至测试 API。要在 Spring Boot 项目中集成 Knife4J,首先需要添加相关依赖,然后配置 YAML 文件以定制化 API 文档界面。

6.1 添加依赖

pom.xml 文件中,添加 Knife4J 的依赖可以使得项目支持 Knife4J 提供的 API 文档界面:

<!-- Knife4J 提供了Swagger增强界面,用于更友好地展示API文档 -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

6.2 YAML 配置

application.yml 文件中,配置 Knife4J 以定制 API 文档的显示方式:

knife4j:
  enable: true # 启用 Knife4J
  documents:
    - group: Test Group # 分组名称
      name: My Documents # 文档名称
      locations: classpath:wiki/*  # 文档位置
  setting:
    language: en-US # 设置文档语言为英文
    # 配置文档页脚信息
    enableFooter: false # 关闭默认页脚
    enableFooterCustom: true # 启用自定义页脚
    footerCustomContent: MIT | [JavGo](https://javgo.cn) # 自定义页脚内容
    # 配置文档头部信息
    enableHomeCustom: true # 启用自定义首页头部
    homeCustomLocation: classpath:wiki/README.md # 指定自定义首页内容文件位置
    # 配置API模型信息显示
    enableSwaggerModels: true # 显示模型信息
    swaggerModelName: My Models # 自定义模型信息的显示名称

这段配置主要包括了启用 Knife4J,定制文档首页的 Header 和 Footer 信息,以及 API 模型的展示。

你可以通过 Markdown 文件来定制 Knife4J 的 API 文档首页。例如,classpath:wiki/README.md

# Document Home

> TIP:自定义 API 文档首页

Welcome to [JavGo](https://www.javgo.cn)

6.3 Knife4J 配置类

在 Spring Boot 项目中集成 Knife4j 提供优雅的 Swagger 文档页面,除了前文提到的依赖添加和 YAML 配置外,还需要创建配置类以定制 Swagger 的 Docket 实例。

import cn.edu.just.hostpital.system.common.ResponseStatus;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Swagger For OpenApi
 *
 * @author javgo.cn
 * @date 2024/1/20
 */
@Configuration
@EnableOpenApi // 启用 Swagger 3.0 版本的 API 文档生成功能
public class OpenApiConfig {

    /**
     * Knife4j 扩展类
     */
    private final OpenApiExtensionResolver openApiExtensionResolver;

    /**
     * API 文档分组名称, 用于区分多个分组(默认为 default)
     */
    private final String GROUP_NAME = "DEV GROUP";

    @Autowired
    public OpenApiConfig(OpenApiExtensionResolver openApiExtensionResolver) {
        this.openApiExtensionResolver = openApiExtensionResolver;
    }

    @Bean
    public Docket openApi() {
        return new Docket(DocumentationType.OAS_30)
                .groupName(GROUP_NAME) // API 文档分组名称, 用于区分多个分组(默认为 default)
                .apiInfo(apiInfo()) // 设置 API 文档的基本信息,这些信息会显示在文档页面上
                .select() // 配置扫描接口的路径和选择器
                .apis(RequestHandlerSelectors.basePackage("cn.edu.just.hostpital.system.controller")) // 指定 Swagger 扫描的包路径,仅生成这个包下面类的 API 文档
                .paths(PathSelectors.any()) // 指定路径选择器,这里是选择任何路径
                .build()
                .globalRequestParameters(getGlobalRequestParameters()) // 全局请求参数
                .globalResponses(HttpMethod.GET, getGlobalResponse()) // 全局响应配置
                .extensions(openApiExtensionResolver.buildExtensions(GROUP_NAME)) // Knife4j 扩展插件
                .extensions(openApiExtensionResolver.buildSettingExtensions()); // Knife4j 全局参数配置
    }

    /**
     * 定义 API 文档的汇总信息,如文档标题、描述、联系人信息、版本号等
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger API") // 文档标题
                .description("DEV Swagger API 3.0") // 文档描述
                .contact(new Contact("javgo", "https://javgo.cn", "javgocn@gmail.com")) // 联系人信息
                .termsOfServiceUrl("https://javgo.cn") // 网站地址
                .version("1.0") // 版本号
                .build();
    }

    /**
     * 定义全局请求参数,全局请求参数将会添加到所有 API 接口中
     */
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder() // 参数配置
                .name("AppKey") // 参数名
                .description("AppKey") // 描述信息
                .in(ParameterType.QUERY) // 参数位置,在此为查询参数
                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))) // 设置参数的数据模型,这里是简单的字符串类型
                .required(false) // 是否必填
                .build());
        return parameters;
    }

    /**
     * 定义全局响应信息,对所有的 GET 请求,都会添加这里定义的响应状态码和描述
     */
    private List<Response> getGlobalResponse() {
        return ResponseStatus.HTTP_STATUS_ALL.stream() // 获取所有预定义的 HTTP 响应状态码及其描述
                .map(httpStatus -> new ResponseBuilder() // 响应配置
                        .code(httpStatus.getResponseCode()) // 响应状态码
                        .description(httpStatus.getDescription()) // 响应状态描述
                        .build())
                .collect(Collectors.toList());
    }
}

在这个配置类中,通过 @Autowired 注解注入了 OpenApiExtensionResolver,Knife4j 提供的扩展解析器,用于增加一些扩展功能和配置。openApi 方法创建了一个 Docket 实例,并通过链式调用进行了一系列配置:

  • 设置 API 文档的分组名称,以便在有多个 Docket 实例时区分。
  • 定义了 API 文档的基本信息,比如标题、描述和联系方式等。
  • 指定了 Swagger 扫描的包路径,限制了 API 文档生成的范围。
  • 添加了全局请求参数和全局响应信息,这些将应用于所有的 API 接口。

此外,配置了 Knife4j 的扩展功能,这包括文档的扩展插件和全局参数配置。

ResponseStatus 枚举定义了一组 API 响应状态码及其描述,这些可以用于在 API 文档中显示标准的 HTTP 状态码信息。枚举的 HTTP_STATUS_ALL 列表包含了所有预定义的状态码,这在 getGlobalResponse 方法中被用来为所有 GET 请求生成全局响应信息。

涉及到的状态码枚举:

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 响应状态枚举
 *
 * @author javgo.cn
 * @date 2024/1/8
 */
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ResponseStatus {

    SUCCESS("200", "success"),
    FAIL("500", "failed"),

    HTTP_STATUS_200("200", "ok"),
    HTTP_STATUS_400("400", "request error"),
    HTTP_STATUS_401("401", "no authentication"),
    HTTP_STATUS_403("403", "no authorities"),
    HTTP_STATUS_500("500", "server error");

    private final String responseCode;
    private final String description;

    public static final List<ResponseStatus> HTTP_STATUS_ALL = Collections.unmodifiableList(Arrays.asList(
            HTTP_STATUS_200,
            HTTP_STATUS_400,
            HTTP_STATUS_401,
            HTTP_STATUS_403,
            HTTP_STATUS_500
    ));

    public static ResponseStatus of(String responseCode) {
        for (ResponseStatus responseStatus : ResponseStatus.values()) {
            if (responseStatus.responseCode.equals(responseCode)) {
                return responseStatus;
            }
        }
        return null;
    }

}

通过这样的配置,Knife4j 将为 Spring Boot 项目提供一个可自定义、功能丰富的 API 文档页面。开发者可以通过这个页面来查看、测试 API 接口,极大地提高了 API 的可视化和交互性。

6.4 Controller

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付宝支付控制器
 *
 * @author javgo.cn
 * @date 2024/1/13
 */
@Api(value = "支付宝支付控制器", tags = "支付宝支付控制器")
@Controller
@RequestMapping("/alipay")
public class AlipayController {

    @Resource
    private AlipayConfig alipayConfig;

    @Resource
    private AlipayService alipayService;

    @ApiOperation("支付宝电脑网站支付")
    @GetMapping("/pcPayment")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "aliPayReq", type = "AliPayReq", value = "支付宝支付请求参数", required = true, dataTypeClass = AliPayReq.class),
            @ApiImplicitParam(name = "response", type = "HttpServletResponse", value = "响应", required = true, dataTypeClass = HttpServletResponse.class)
    })
    public void pcPayment(AliPayReq aliPayReq, HttpServletResponse response) throws IOException {
        response.setContentType(ContentType.TEXT_HTML.getContentType() + ";charset=" + alipayConfig.getCharset());
        Result<?> Result = alipayService.initiatePcPayment(aliPayReq);
        response.getWriter().write(Result.getData().toString());
        response.getWriter().flush();
        response.getWriter().close();
    }

    @ApiOperation("支付宝手机网站支付")
    @GetMapping("/mobilePayment")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "aliPayReq", type = "AliPayReq", value = "支付宝支付请求参数", required = true, dataTypeClass = AliPayReq.class),
            @ApiImplicitParam(name = "response", type = "HttpServletResponse", value = "响应", required = true, dataTypeClass = HttpServletResponse.class)
    })
    public void mobilePayment(AliPayReq aliPayReq, HttpServletResponse response) throws IOException {
        response.setContentType(ContentType.TEXT_HTML.getContentType() + ";charset=" + alipayConfig.getCharset());
        Result<?> Result = alipayService.initiateMobilePayment(aliPayReq);
        response.getWriter().write(Result.getData().toString());
        response.getWriter().flush();
        response.getWriter().close();
    }

    @ApiOperation("支付宝支付通知")
    @PostMapping("/notify")
    @ResponseBody
    @ApiImplicitParam(name = "request", type = "HttpServletRequest", value = "请求", required = true, dataTypeClass = HttpServletRequest.class)
    public Result<?> processPaymentNotification(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        requestParams.keySet().forEach(r -> params.put(r, request.getParameter(r)));
        return alipayService.processPaymentNotification(params);
    }

    @ApiOperation("查询支付状态")
    @GetMapping("/queryPaymentStatus")
    @ResponseBody
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", type = "String", value = "商户订单号", required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "tradeNo", type = "String", value = "支付宝交易号", required = true, dataTypeClass = String.class)
    })
    public Result<?> queryPaymentStatus(String outTradeNo, String tradeNo) {
        return alipayService.queryPaymentStatus(outTradeNo, tradeNo);
    }
}

6.5 测试

地址:http://localhost:8080/doc.html

自定义的用户主页:

Model 模型:

全局参数和配置:

API 文档语言:

API 文档 Footer 信息:

API 文档分组:

API 接口:

离线文档导出:

7.无侵入的 Smart-Doc

TIP:Smart-Doc 的出现主要是为了解决侵入性和依赖性问题,这些问题是在使用 Swagger 这类工具时经常遇到的。Smart-Doc 提供了一种更加轻量化、非侵入式的方式来生成文档,这对于维护代码的清洁度和减少对特定工具的依赖来说是非常有价值的。

7.1 Smart-Doc 的非侵入式特点

  1. 非侵入式: Smart-Doc 并不要求在代码中添加专门的注解来生成文档,这意味着你可以在不修改任何业务代码的情况下集成Smart-Doc。这对于希望保持代码干净和减少外部依赖的项目来说是一大优势。
  2. 基于标准 Java 注释: 只需要按照 Java-doc 的标准来编写注释,Smart-Doc 就能够自动地生成文档。这利用了 Java 项目中已有的注释,而不需要学习和使用额外的注释方法。
  3. 智能化的推导机制: Smart-Doc 通过分析源码中的接口定义来推导接口的请求和响应结构,包括复杂的泛型和异步返回类型。这降低了需要手动编写文档的工作量。

7.2 Smart-Doc 的特性

  1. 支持多种框架: Smart-Doc 可用于 Spring MVC, Spring Boot, Spring Boot Web Flux, 和 Feign 等框架。
  2. 支持异步返回类型: 如 Callable、Future、CompletableFuture 等。
  3. 集成 JSR303 验证: 支持 JavaBeans 的 JSR303 参数校验规范,包括分组验证。
  4. 自动化数据生成: 自动为 JSON 请求生成模拟参数,生成 JSON 返回值示例,并支持常用字段的有效模拟值。
  5. 多种文档输出格式: 支持 Markdown、HTML5、Asciidoctor、Postman Collection、OpenAPI 3.0 等。
  6. 外部源码加载: 支持从项目外部加载源代码来生成代码注释,这对于处理多模块和依赖第三方库的项目来说尤为重要。
  7. 插件支持: 支持 Maven 和 Gradle 插件,轻松集成到现有项目。
  8. RPC 接口文档支持: 支持生成 Apache Dubbo RPC 接口文档。
  9. 调试支持: 提供了一个 HTML5 页面,支持文件上传、下载测试。

TIP:尽管 Smart-Doc 提供了非侵入式的接口文档生成方案,但 Swagger+OpenAPI 的技术栈因其生态成熟、社区活跃以及标准化程度高(尤其是对 OpenAPI 规范的支持)而被广泛采用。技术选择应基于项目需求、团队习惯以及对工具特性的考量来进行平衡决策。对于那些寻求零侵入、零学习曲线并且满足文档生成需求的场景,Smart-Doc 是一个非常有吸引力的选项。

7.3 案例:Spring Boot 集成 Smart-Doc

smart-doc-maven-plugin

TIP:更多示例可参考官方配置文档

从 smart-doc 1.7.9 版本开始,官方就提供了 Maven 插件,它允许用户通过在项目中集成该插件,然后执行 Maven 命令来直接生成 API 文档。这种方式可以有效简化文档的生成过程,它支持输出 HTML、Markdown 等多种格式的文档,并且可以配置成在构建过程中自动生成。

以下是一个基于 smart-doc-maven-plugin 的示例配置,用于生成 API 文档:

<plugin>
  <groupId>com.github.shalousun</groupId>
  <artifactId>smart-doc-maven-plugin</artifactId>
  <version>${smart-doc.version}</version> <!-- 2.4.8 -->
  <configuration>
      <!-- 生成文档使用的配置文件路径 -->
      <configFile>src/main/resources/smart-doc.json</configFile>
      <!-- 指定项目名称,若配置文件和此处都未设置,插件将默认使用 pom.xml 中的 <name> 作为项目名称 -->
      <!-- <projectName>${project.description}</projectName> -->
      <!-- 排除列表:指定不需要 smart-doc 加载的依赖库,通常用于解决依赖分析错误或减少文档构建时间 -->
      <excludes>
          <!-- 排除格式应为 groupId:artifactId,支持使用通配符*,例如:com.alibaba:* -->
          <exclude>com.alibaba:fastjson</exclude>
      </excludes>
      <!-- 包含列表:配置 smart-doc 插件加载的外部依赖源码,优化插件的加载速度 -->
      <!-- 仅加载列入此处的依赖源码,而非自动加载所有依赖 -->
      <!-- smart-doc 可自动分析依赖树加载所有依赖源码,为优化构建效率,建议使用这个列表显式指定需要加载的依赖 -->
      <includes>
          <!-- 包含格式应为 groupId:artifactId,支持通配符*,例如:com.alibaba:* -->
          <include>com.alibaba:fastjson</include>
      </includes>
  </configuration>
  <executions>
      <execution>
          <!-- 生命周期阶段:决定在 Maven 的哪个阶段执行 smart-doc 文档生成 -->
          <!-- 如果不需要在编译阶段自动执行 smart-doc,可以注释掉下面的 <phase> 标签 -->
          <phase>compile</phase>
          <goals>
              <!-- 指定 smart-doc 目标生成的文档类型 -->
              <!-- smart-doc 提供多种文档格式生成的目标,如 html、openapi、markdown 等 -->
              <!-- 可根据需要配置相应的目标来生成不同格式的 API 文档 -->
              <goal>html</goal>
          </goals>
      </execution>
  </executions>
</plugin>
smart-doc.json

其中 src/main/resources/smart-doc.json 是 smart-doc 加载的配置文件:

{
  "serverUrl": "http://127.0.0.1", // 服务器地址。建议在导出 Postman 集合时使用 “http://{{server}}”,以便在 Postman 中直接设置环境变量。
  "pathPrefix": "", // 设置 API 路径前缀,非必须。例如配置 Servlet 的 Context Path(@since 2.2.3)。
  "isStrict": false, // 是否开启严格模式。在严格模式下,smart-doc 将严格按照注释来生成文档。
  "allInOne": true, // 是否将所有 API 文档合并到单个文件。推荐设置为 true,便于文档管理。
  "outPath": "doc/smart-doc", // 文档输出路径。指定生成的文档应放置的文件系统路径。(用户自定义)
  "coverOld": true, // 是否覆盖旧文件。主要用于更新 Markdown 文档时覆盖旧版。
  "createDebugPage": true, // 是否生成可用于测试的 HTML 页面。适用于 AllInOne 模式。
  "packageFilters": "", // Controller 包过滤。使用正则表达式指定包路径,以过滤不需要生成文档的包。
  "md5EncryptedHtmlName": false, // 是否对每个 Controller 生成的 HTML 文件名进行 MD5 加密。
  "style": "xt256", // 代码高亮样式。基于 highlight.js,可选择多种样式。若喜欢统一配色,可不进行设置。
  "projectName": "hostpital-system-demo-smart-doc", // 项目名称。如果不设置,则插件默认获取 pom.xml 中的 projectName。(用户自定义)
  "skipTransientField": true, // 是否跳过 transient 修饰的字段。目前未实现该功能。
  "sortByTitle": false, // 是否按接口标题排序。默认为 false。
  "showAuthor": true, // 是否显示接口作者名称。默认为 true,可以关闭。
  "requestFieldToUnderline": true, // 是否将驼峰式请求字段转换为下划线形式显示在文档中。
  "responseFieldToUnderline": true, // 是否将驼峰式响应字段转换为下划线形式显示在文档中。
  "inlineEnum": true, // 是否在参数表中展示枚举详情。默认关闭。
  "recursionLimit": 7, // 允许递归执行的次数上限。用于避免某些对象解析时出现无限循环,默认为7。
  "allInOneDocFileName": "index.html", // 自定义输出文档的文件名。(用户自定义)
  "requestExample": "true", // 是否在文档中展示请求示例,默认为 true。
  "responseExample": "true", // 是否在文档中展示响应示例,默认为 true。

  // 下面配置了一系列高级设置,包括忽略请求参数、数据字典、错误码列表等。

  "ignoreRequestParams": [ // 忽略特定请求参数。指定不想生成文档的参数类型。
    "org.springframework.ui.ModelMap"
  ],
  "dataDictionaries": [ // 数据字典配置,用于生成固定值的描述信息,没有需求可以不配置。
    {
      "title": "http状态码字典", // 字典标题
      "enumClassName": "cn.edu.just.hostpital.system.common.ResponseStatus", // 枚举类的全限定名(用户自定义)
      "codeField": "responseCode", // 响应码字段名(用户自定义)
      "descField": "description" // 响应描述字段名(用户自定义)
    }
  ],
  "errorCodeDictionaries": [ // 错误码字典配置,用于描述各种错误码。
    {
      "title": "错误码示例标题", // 错误码字典标题
      "enumClassName": "cn.edu.just.hostpital.system.common.ResponseStatus", // 枚举类的全限定名(用户自定义)
      "codeField": "responseCode", // 响应码字段名(用户自定义)
      "descField": "description" // 响应描述字段名(用户自定义)
    }
  ],
  "revisionLogs": [ // 文档变更记录,记录API文档的版本和更新历史(用户自定义)。
    {
      "version": "1.1", // 版本号
      "revisionTime": "2024-01-21 22:12:01", // 更新时间
      "status": "update", // 更新状态
      "author": "javgo", // 更新作者
      "remarks": "initial user API documentation" // 更新备注
    },
    {
      "version": "1.2",
      "revisionTime": "2024-01-21 22:12:02",
      "status": "update",
      "author": "javgo",
      "remarks": "added address API documentation"
    }
  ],
  "customResponseFields": [ // 自定义响应字段,用于第三方库字段的手动注释(非必须)。
    {
      "name": "code", // 覆盖响应码字段
      "desc": "响应代码", // 字段描述
      "ownerClassName": "org.springframework.data.domain.Pageable", // 字段所属类名
      "ignore": true, // 是否忽略该字段
      "value": "00000" // 字段值
    }
  ],

  // 请求头和请求参数的全局配置。

  "requestHeaders": [ // 配置全局请求头,没有需求可以不配置。
    {
      "name": "token", // 请求头名称
      "type": "string", // 请求头类型
      "desc": "desc", // 请求头描述信息
      "value":"token请求头的值", // 不设置默认null
      "required": false, // 是否必须
      "since": "-", // 什么版本添加的该请求头
      "pathPatterns": "/app/test/**", // 请看 https://gitee.com/smart-doc-team/smart-doc/wikis/请求头高级配置?sort_id=4178978
      "excludePathPatterns":"/app/page/**" // 请看 https://gitee.com/smart-doc-team/smart-doc/wikis/请求头高级配置?sort_id=4178978
    },
    {
      "name": "appkey", // 请求头
      "type": "string", // 请求头类型
      "desc": "desc", // 请求头描述信息
      "value":"appkey请求头的值", // 不设置默认null
      "required": false, // 是否必须
      "pathPatterns": "/test/add,/testConstants/1.0", // 正则表达式过滤请求头,url 匹配上才会添加该请求头,多个正则用分号隔开
      "since": "-" // 什么版本添加的该请求头
    }
  ],
  "requestParams": [ // 配置全局请求参数,没有需求可以不配置。
    {
      "name": "configPathParam", // 请求名称
      "type": "string", // 请求类型
      "desc": "desc", // 请求描述信息
      "paramIn": "path", // 参数所在位置 header-请求头, path-路径参数, query-参数
      "value":"testPath", // 不设置默认null
      "required": false, // 是否必须
      "since": "2.2.3", // 什么版本添加的该请求
      "pathPatterns": "/app/test/**", //请看 https://gitee.com/smart-doc-team/smart-doc/wikis/请求高级配置?sort_id=4178978
      "excludePathPatterns":"/app/page/**" //请看 https://gitee.com/smart-doc-team/smart-doc/wikis/请求高级配置?sort_id=4178978
    }
  ],
  "responseBodyAdvice": { // 全局响应体的配置,用于统一封装API响应格式(smart-doc 1.9.8 +),建议不要随便配置,请根据项目的实际情况配置,可以使用 ignoreResponseBodyAdvice 忽略该配置。
    "className": "cn.edu.just.hostpital.system.common.Result" // 通用响应封装类
  }
}

注意:

  1. 使用时请去掉上述注释;
  2. 请根据注释内容进行合理配置。
生成 smart-doc 文档

smart-doc 插件支持生成多种格式的文档,包括 HTML、Markdown、AsciiDoc 以及 Postman 集合和 OpenAPI 规格。

打开终端或命令提示符,并确保已经定位到 Maven 项目根目录。下面是一系列 Maven 命令,通过这些命令,能够生成不同格式的文档:

生成 HTML 文档:

mvn -Dfile.encoding=UTF-8 smart-doc:html

生成 Markdown 文档:

mvn -Dfile.encoding=UTF-8 smart-doc:markdown

生成 AsciiDoc 文档:

mvn -Dfile.encoding=UTF-8 smart-doc:adoc

生成 Postman 集合数据:

mvn -Dfile.encoding=UTF-8 smart-doc:postman

生成遵循 OpenAPI 3.0+ 规格的文档(smart-doc-maven-plugin 1.1.5 及以上版本):

mvn -Dfile.encoding=UTF-8 smart-doc:openapi

在执行上述任何一个命令之前,请确保你的项目已经正确配置了 smart-doc 插件。以上命令中的 -Dfile.encoding=UTF-8 确保文档在生成过程中使用 UTF-8 编码,以避免字符编码问题。

在 IDEA 中,也可以通过 maven 插件快速构建:

构建成功你会在控制台看到如下输出日志:

[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------< cn.edu.just:hostpital-system >--------------------
[INFO] Building hostpital-system 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> smart-doc-maven-plugin:2.4.8:html (default-cli) > compile @ hostpital-system >>>
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ hostpital-system ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 931 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hostpital-system ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] <<< smart-doc-maven-plugin:2.4.8:html (default-cli) < compile @ hostpital-system <<<
[INFO] 
[INFO] 
[INFO] --- smart-doc-maven-plugin:2.4.8:html (default-cli) @ hostpital-system ---
[INFO] ------------------------------------------------------------------------
[INFO] Smart-doc Start preparing sources at: 2024-01-21 14:10:07
[INFO] Artifacts that the current project depends on: ["org.springframework.boot:spring-boot-starter","org.springframework.boot:spring-boot-starter-web","org.springframework.boot:spring-boot-starter-test","org.springframework.boot:spring-boot-starter-data-redis","mysql:mysql-connector-java","com.baomidou:mybatis-plus-boot-starter","com.github.jsqlparser:jsqlparser","com.baomidou:mybatis-plus-generator","org.apache.velocity:velocity-engine-core","com.github.xiaoymin:knife4j-spring-boot-starter","com.github.pagehelper:pagehelper","com.alibaba:druid-spring-boot-starter","com.alibaba:fastjson","org.projectlombok:lombok","org.apache.commons:commons-collections4","com.google.guava:guava","cn.hutool:hutool-all","com.github.dozermapper:dozer-core","com.alipay.sdk:alipay-sdk-java"]
[INFO] Smart-doc has loaded the source code path: [{"path":"/Users/javgo/develop/IDEA/project/hostpital-system/src/main/java"}]
[INFO] Smart-doc Starting Create API Documentation at: 2024-01-21 14:10:08
[INFO] API documentation is output to => /Users/javgo/develop/IDEA/project/hostpital-system/doc/smart-doc
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.412 s
[INFO] Finished at: 2024-01-21T14:10:09+08:00
[INFO] ------------------------------------------------------------------------

Process finished with exit code 0

以 HTML 为例,构建后的 index.html 如下:

双击 index.html 打开即可看到构建成功的文档:

可以看到 smart-doc 还自动提供了 mock 的数据,以及测试接口的按钮:

还包含自定义的错误码列表、返回枚举类型等:

7.3 smart-doc 的注释驱动文档生成

我们前面说过,在软件开发中,API 文档的质量对于维护和交流至关重要。smart-doc 是一个以注释为基础,用于生成 API 文档的工具,它摒弃了如 Swagger 注解式的侵入,提供了一种简洁高效的文档生成方式。

smart-doc 通过解析 javadoc 注释来生成文档内容,它优先采用了 javadoc 原生的 tag,确保了文档与代码的同步更新。但是有些时候注释的信息是有限的,那么 smart-doc 如何从注释拓展文档内容呢?

以下是 smart-doc 使用的一些 javadoc 的注释 tag 及其描述:

tag描述
@param主要用于描述接口层的参数。对于简单类型的参数,必须在 @param 中提供注释描述,而对于实体(Entity)类型,smart-doc 会自动提取字段的注释。
@deprecated该标记用于指示某个接口或方法已经过时,作用与 @Deprecated 注解相同。
@apiNote一个在 Java 中新增的文档 tag。在 smart-doc 中,它被用作方法的详细描述,可以包含较长的注释说明。

然而,仅靠原生的 tag 并不能满足所有文档生成的需求,因此 smart-doc 引入了一系列自定义的 tag 来进一步扩展功能:

tag描述
@ignore可用于忽略请求参数对象中的某些字段,或者完全忽略某个接口方法不输出到文档中。(关于响应字段忽略的请看忽略响应字段 )如果加到方法上,则接口方法不会输出到文档。从1.8.4开始 @ignore 支持添加到 Controller 上进行忽略不想生成文档的接口类。
@required在不使用 JSR303 参数验证规范的情况下,该 tag 可以标注字段为必须,指示 smart-doc 在输出参数列表时将其设置为 true
@mock允许设置对象基本类型字段的自定义文档展示值,避免 smart-doc 生成随机值,使文档更加贴近实际应用。
@dubbo用于支持 smart-doc 扫描 Dubbo RPC 定义的接口,生成相应的文档。
@restApi用于支持 smart-doc 扫描 Spring Cloud Feign 定义的接口,生成相应的文档。
@order用于自定义 Controller 接口或 API 入口的排序序号。
@ignoreResponseBodyAdvice用于忽略 ResponseBodyAdvice 设置的响应包装类。
@download标注文件下载方法,支持在生成的 debug 页面中进行文件下载测试。
@page标注用于渲染返回静态页面的方法。
@ignoreParams标注在 Controller 方法上,忽略不想在文档中显示的参数,多个参数用逗号隔开。
@response允许定义返回的 json example,建议在返回基础类型时使用。如:Result<String> 类型这种泛型是简单原生类型的响应。
@tag用于将 Controller 方法分类或指定 Controller 为一个或多个分类。

可见,通过 smart-doc,开发者可以依赖于代码中的注释来生成细致、准确的 API 文档,从而提供更加清晰的接口说明和更高效的团队协作。

7.4 在 Maven 多模块项目中使用 smart-doc 插件

在 Maven 多模块项目中使用 smart-doc 插件时,适当的配置能够确保文档生成工作顺利进行。以下是一些根据项目结构配置插件的最佳实践。

完全的父子级关系的 Maven 项目结构

假设有如下的项目结构:

├─parent
│  ├─common
│  │  └─pom.xml
│  ├─web1
│  │  └─pom.xml
│  ├─web2
│  │  └─pom.xml
│  └─pom.xml

在这种情况下,web1web2 依赖于 common 模块,且只能在根目录 parent 下执行编译命令。smart-doc 插件应当放置在根 pom.xml 中,同时在 web1web2 中放置各自的 smart-doc.json 配置文件。

使用 -pl 参数指定生成特定模块的文档,例如:

# 为 web1 模块生成 API 文档
mvn smart-doc:html -Dfile.encoding=UTF-8  -pl :web1 -am

# 为 web2 模块生成 API 文档
mvn smart-doc:html -Dfile.encoding=UTF-8  -pl :web2 -am
非严格父子级构建的项目

如果 common 模块没有在其 pom.xml 中定义 parent,并且 web1web2 可以在各自的目录下使用 Maven 命令独立编译,那么可以直接在 web1web2pom.xml 中分别添加 smart-doc 插件。

在这种设置下,每个模块都可以控制自己的文档生成过程,而不必依赖于父模块。这样做的好处是,每个模块的构建和文档生成过程更加独立和灵活。

注意:

  • 使用 smart-doc 插件并没有一成不变的模式,关键是熟悉 Maven 的操作命令,并根据自己项目的具体情况来调整配置。
  • smart-doc-maven-plugin 1.2.0 开始,插件能够自动分析并加载模块的依赖来生成文档,而不会将所有模块的接口文档合并成一个文件。

以上实践可以作为在 Maven 多模块项目中配置 smart-doc 插件的参考,但最终的配置应考虑项目具体需求和开发团队的工作流程。

7.5 调试 smart-doc 文档生成问题指南

当你在使用 smart-doc-maven-plugin 插件生成 API 文档时遇到问题,调试是发现并解决问题的有效手段。下面给一些官方建议的调试步骤。

第一步:添加 smart-doc 依赖

为了进行有效的调试,首先确保在项目的 pom.xml 中添加了 smart-doc 的依赖,如下所示:

<!-- Smart-doc 文档生成工具 -->
<dependency>
    <groupId>com.github.shalousun</groupId>
    <artifactId>smart-doc</artifactId>
    <version>${smart-doc.version}</version>
    <scope>test</scope>
</dependency>

注意:这里使用的 smart-doc 的版本应与 smart-doc-maven-plugin 插件依赖的版本保持一致。

第二步:添加断点

在你的 IDE(例如 IntelliJ IDEA)中,找到 smart-doc 代码中你想要调试的部分,并添加断点。

第三步:Debug 模式运行 Maven 构建目标

在 IntelliJ IDEA 中运行 Maven 插件的 debug 模式非常简单,只需找到 Maven 项目窗口,然后选择相应的插件目标进行调试。设置好后,IDE 将在断点处暂停,从而允许你查看和调试代码的执行。

这样设置之后,当你通过 IDE 运行 Maven 目标时,代码将在指定的断点处停止,这将使你能够检查变量的状态、执行流程和其他对调试至关重要的信息。

附加提示

如果你需要调试 smart-doc-maven-plugin 插件本身的源码而不仅是 smart-doc,则需要将插件作为一个依赖项添加到你的项目中,如下所示:

<dependency>
    <groupId>com.github.shalousun</groupId>
    <artifactId>smart-doc-maven-plugin</artifactId>
    <version>${smart-doc.version}</version>
</dependency>

通过上述步骤,你可以更容易地找出文档生成问题的根源,并进行修复。务必确保你正在使用的 smart-docsmart-doc-maven-plugin 的版本是最新的,因为开发者可能已经解决了你当前遇到的问题。


参考资料:

  • https://fishead.gitbook.io/openapi-specification-zhcn-translation/3.0.0.zhcn#revisionHistory
  • https://github.com/OAI/OpenAPI-Specification
  • https://swagger.io/
  • https://doc.xiaominfo.com/knife4j/documentation/
  • https://smart-doc-group.github.io/#/zh-cn/plugins/maven_plugin
  • https://www.pdai.tech/md/spring/springboot/springboot-x-interface-doc.html
  • https://www.pdai.tech/md/spring/springboot/springboot-x-interface-doc-smart.html
文章来源:https://blog.csdn.net/ly1347889755/article/details/135729586
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。