DDD第十弹:替换Swagger,使用这款文档工具,真香~

大家好,我是飘渺。今天,我们将探讨如何在 Spring Cloud 体系下集成 SpringDoc 来生成 API 文档。这是我们 DDD&微服务系列文章的一部分,欢迎继续关注。

在之前的 Spring Cloud 微服务系列文章中,我整合了 Swagger 来实现接口文档功能。然而,在 DailyMart 项目中,由于使用的是 Spring Boot 3.x 版本,再次使用 Swagger 可能会遇到一些问题。目前 Spring Boot 3.x 将包 javax 下的所有内容迁移到了 jakarta 包下,例如 HttpServletRequest,而 Swagger 仍然使用的是 javax 包,导致不兼容的问题。因此,我们将使用 SpringDoc 来替代之前的 Swagger 组件。

1. 集成SpringDoc

在 Spring Boot 3.0 中,集成 SpringDoc 非常简单,只需要遵循以下几个步骤:

1.1 引入依赖

首先,在基础设施层的 dailymart-customer-infrastructure 模块的 pom.xml 文件中引入 SpringDoc 的依赖:

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

1.2 添加文档注解

接下来,我们可以在项目的接口上添加相应的接口注解,以之前介绍的用户接口接口为例:

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
//@Validated
@Tag(name = "CustomerController",description = "C端用户管理")
public class CustomerUserController {

    private final CustomerUserService customerUserService;


    @Operation(summary = "用户注册接口")
    @PostMapping("/api/customer/register")
    public UserRegistrationDTO register(@RequestBody @Valid UserRegistrationDTO customerDTO){
        return customerUserService.register(customerDTO);
    }


    @Operation(summary = "用户登录接口")
    @Parameters(value = {
            @Parameter(name = "loginType",description = "登录类型",example = "USERNAME_PASSWORD"),
            @Parameter(name = "userName",description = "用户账号"),
            @Parameter(name = "phone",description = "手机号码"),
            @Parameter(name = "smsCode",description = "短信验证码"),
            @Parameter(name = "email",description = "邮箱"),
            @Parameter(name = "emailCode",description = "邮箱验证码")
    })
    @PostMapping("/api/customer/login")
    public UserLoginRespDTO login(@RequestBody Map<String, String> parameters){
        UserLoginDTO loginDTO = LoginDTOFactory.getLoginDTO(parameters);
        return customerUserService.login(loginDTO);
    }
}

以下是SpringDoc中常用的几个注解,大家可以自行测试

注解含义
@Tag用在Controller类上,描述此Controller的信息
@Operation用在Controller的方法里,描述此Api的信息
@Parameter用在Controller方法里的参数上,描述参数信息
@Parameters用在Controller方法里的参数上
@Schema用于DTO,以及DTO的属性上
@ApiResponse用在Controller方法的返回值上
@ApiResponses用在Controller方法的返回值上
@Hidden用在各种地方,用于隐藏其api

1.3 优化包装类

在之前的文章《DailyMart07:DDD 中统一返回格式与全局异常处理》中,我们实现了包装响应体的功能,使所有对外的接口自动返回 Result 包装类。然而,集成了 SpringDoc 后,我们需要过滤掉 SpringDoc 的接口包装,以避免异常。我们可以通过如下方式进行优化:

@Slf4j
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;


    /**
     *  此处可以通过判断决定哪些响应需要包装,有两种办法
     *  1. 指定需要返回的接口,就DailyMart而言,接口项目包的地址前缀都是 com.jianzh5.dailymart
     *  2. 如果返回的接口类型是springdoc的,则不转换,returnType.getDeclaringClass().getName().contains("springdoc")
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {

//        boolean supports = returnType.getContainingClass().getPackage().getName().startsWith("com.jianzh5.dailymart");
//        return supports;
        log.info("supports package:{}",returnType.getDeclaringClass().getName());
        boolean supports = returnType.getDeclaringClass().getName().contains("springdoc");
        return !supports;

    }
}

完成了上述步骤后,我们已经成功集成了 SpringDoc 文档。此时,在浏览器中访问 http://localhost:8081/swagger-ui/index.html,即可查看用户模块的接口文档。你还可以进入具体的接口页面进行调试,非常方便。

不过,此时生成的文档仍然使用默认值,接下来我们将介绍如何进行各种自定义配置。

1.4 自定义SpringDoc配置

为了配置文档的基础属性,我们可以在基础设施层创建一个名为 OpenApiConfig 的配置类:

@Configuration
public class OpenApiConfig {
    
    @Bean
    public OpenAPI openAPI(){
        return new OpenAPI()
                .info(apiInfo())
                .externalDocs(new ExternalDocumentation()
                        .description("我的知识星球")
                        .url("https://wx.zsxq.com/dweb2/index/group/28851454115211")
                );
    }

    private Info apiInfo(){
        return new Info().title("DDD微服务商城")
                .description("C端用户模块")
                .version("v1.0")
                .contact(new Contact()
                        .name("Java日知录")
                        .url("https://javadaily.cn")
                        .email("jam.zhang11@gmail.com")
                )
                .license(new License()
                        .name("许可协议")
                        .url("https://www.gnu.org/licenses/")
                );
    }
}

在上述代码中,有很多配置项可以根据自己的需求进行调整。例如,你可以根据代码和截图对照,按照自己的要求进行配置。

image-20230802162445285

另外,如果在生产环境中需要关闭接口文档功能,集成 SpringDoc 后,你可以通过以下配置进行关闭:

springdoc:
  apiDocs:
    enabled: true

2. 启用Knif4j增强

在之前使用 Swagger 时,许多人对 Swagger UI 的界面风格不太满意,更倾向于集成 Knife4j 的 UI。SpringDoc 也可以集成 Knife4j,步骤如下:

2.1 引入依赖

引入 Knife4j 的依赖,不再需要单独引入 SpringDoc 的依赖。在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

此时,你可以通过访问 http://localhost:8081/doc.html 来查看 Knife4j 的文档页面,原 Swagger 页面仍然可以访问。

image-20230803152322112

3. 网关聚合

DailyMart 是一个微服务项目,包含多个服务。为了避免每次都进入单独的项目模块查看接口文档,我们可以在网关层进行聚合。

从 Knife4j 4.0 版本开始,在网关层进行文档聚合变得非常方便。下面是相关的步骤:

3.1 引入依赖

在网关服务模块 dailymart-gateway 中添加如下依赖:

<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>knife4j-gateway-spring-boot-starter</artifactId>
	<version>4.1.0</version>
</dependency>

3.2 修改配置

application.yaml 配置文件中添加文档聚合的相关配置,如果需要其他配置,请参考官方手册:doc.xiaominfo.com/docs/middle…

knife4j:
  gateway:
    enabled: true
    # 指定服务发现的模式聚合微服务文档,并且是默认`default`分组
    strategy: discover
    discover:
      enabled: true
      # 指定版本号(Swagger2|OpenAPI3)
      version : openapi3
      # 需要排除的微服务(eg:网关服务)
      excluded-services:
        - gateway-service
      service-config:
        customer-service:
          order: 1
          # 前端显示名称
          group-name: 用户服务接口
        order-service:
          order: 2
          # 前端显示名称
          group-name: 订单服务接口

完成上述两步后,重新启动网关服务。在 DailyMart 项目中,网关服务的端口为 9090。现在,你只需要访问 http://10.5.103.34:9090/doc.html 就可以在网关聚合后查看接口文档了。

image-20230803202224984

4. 构建公共模块

在 DailyMart 项目中,涵盖多个服务。按照目前的做法,每个服务都需要单独引入 Knife4j 的依赖并创建 SpringDoc 的配置类。然而,我们可以将这些重复的工作封装成一个公共的自定义 Starter。这样,任何需要使用这些文档功能的模块只需引入该 Starter 即可。

4.1 创建自定义Starter

在项目中创建一个名为 dailymart-doc-spring-boot-starter 的自定义 Starter 模块:

image.png

创建配置类 DocAutoConfiguration,内容如下:

@SpringBootConfiguration
@EnableConfigurationProperties(DailyMartDocProperties.class)
@ConditionalOnProperty(
        name = {"springdoc.apiDocs.enabled"},
        havingValue = "true"
)
public class DocAutoConfiguration {
    private final DailyMartDocProperties properties;
    public DocAutoConfiguration(DailyMartDocProperties properties) {
        this.properties = properties;
    }


    @Bean
    public OpenAPI openAPI(){
        return new OpenAPI()
                .info(apiInfo())
                .externalDocs(new ExternalDocumentation()
                        .description("我的知识星球")
                        .url("https://wx.zsxq.com/dweb2/index/group/28851454115211")
                );
    }

    private Info apiInfo(){
        return new Info()
                .title(properties.getTitle())
                .description(properties.getDescription())
                .version(properties.getVersion())
                .contact(new Contact()
                        .name("Java日知录")
                        .url("https://javadaily.cn")
                        .email("jam.zhang11@gmail.com")
                )
                .license(new License()
                        .name("许可协议")
                        .url("https://www.gnu.org/licenses/")
                );
    }

}

在这个配置类中,通过引入 DailyMartDocProperties 来填充文档的标题和描述,其代码如下:

@Component
@ConfigurationProperties(
    prefix = "dailymart.doc"
)
@Data
public class DailyMartDocProperties {
    private String title;
    private String description;
    private String version;
}

当然,在SpringBoot2.7以后自定义Starter,还需要在文件/resource/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中导入配置类

com.jianzh5.dailymart.springboot.starter.doc.configuration.DocAutoConfiguration

4.2 其他模块引入自定义Starter

构建了自定义 Starter 后,其他模块就不再需要单独引入 Knife4j 的依赖和 SpringDoc 的配置类了。而是只需引入公共的 dailymart-doc-spring-boot-starter,并在配置文件中指定当前模块的标题、描述和版本,非常方便。

首先在各个微服务的基础设施层模块中的 pom.xml 中添加自定义starter的依赖:

<dependency>
	<groupId>com.jianzh5</groupId>
	<artifactId>dailymart-doc-spring-boot-starter</artifactId>
	<version>${project.version}</version>
</dependency>

然后在模块的配置文件中指定自定义 Starter 的属性:

dailymart:
  doc:
    title: "DDD微服务商城"
    description: "C端用户模块"
    version: "V1.0"

小结

在本文中,我们深入研究了在 Spring Cloud 微服务中集成 SpringDoc 生成 API 文档的过程。我们逐步介绍了如何通过替代 Swagger 解决版本兼容性问题,并通过自定义配置来优化生成的文档。我们还探讨了如何使用 Knife4j 提升文档 UI,以及在网关中聚合多个服务的文档。最后,我们引入了构建自定义 Starter 的方法,实现了在不同模块中轻松引入文档功能。

DDD&微服务系列源码已经上传至GitHub,如果需要获取源码地址,请关注公号 java日知录 并回复关键字 DDD 即可。

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务