You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过SpringDoc OpenAPI为两个模块暴露单个Swagger-UI

单个Swagger-UI展示多模块API规格的解决方案

你遇到的核心问题是:rest1的Swagger-UI要展示分属独立容器的rest1(UserController)和rest2(ActionController)的API,但带Swagger注解的是rest-commons里的Spec接口,实际控制器只有@RestController,SpringDocUtils.getConfig().addRestControllers()不支持加载接口类。下面给几个可行的解决思路:

方案1:手动构建OpenAPI,加载Spec接口的注解

直接通过代码读取rest-commons里Spec接口的Swagger注解,手动构建API文档并注册到SpringDoc中,不需要依赖控制器的扫描逻辑。

实现步骤:

  1. 确保rest1引入springdoc-openapi-starter-webmvc-ui依赖(适配Spring Boot版本),同时依赖rest-commons模块。
  2. 创建Swagger配置类,通过GroupedOpenApi注册两个模块的API分组,用OpenApiCustomiser解析Spec接口的注解:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.tags.Tag;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class MultiModuleSwaggerConfig {

    // 注册用户模块API分组
    @Bean
    public GroupedOpenApi userApiGroup() {
        return GroupedOpenApi.builder()
                .group("用户管理API")
                .addOpenApiCustomiser(buildUserApi())
                .build();
    }

    // 注册操作模块API分组
    @Bean
    public GroupedOpenApi actionApiGroup() {
        return GroupedOpenApi.builder()
                .group("操作日志API")
                .addOpenApiCustomiser(buildActionApi())
                .build();
    }

    // 解析UserControllerSpec的注解,构建API定义
    private OpenApiCustomiser buildUserApi() {
        return openApi -> {
            // 设置API基础信息
            openApi.info(new Info().title("用户管理API").version("v1.0"));

            Paths paths = new Paths();
            Method[] specMethods = UserControllerSpec.class.getDeclaredMethods();
            for (Method method : specMethods) {
                Operation apiOperation = new Operation();
                
                // 读取@Operation注解的摘要和描述
                if (method.isAnnotationPresent(Operation.class)) {
                    Operation ann = method.getAnnotation(Operation.class);
                    apiOperation.summary(ann.summary());
                    apiOperation.description(ann.description());
                    // 可扩展处理@Parameter、@ApiResponse等注解
                }

                // 从Spec接口的@RequestMapping读取路径(如果有的话),这里简化为方法名对应路径
                String path = "/api/users/" + method.getName();
                paths.addPathItem(path, new PathItem().get(apiOperation));
            }
            openApi.paths(paths);

            // 添加标签
            List<Tag> tags = new ArrayList<>();
            if (UserControllerSpec.class.isAnnotationPresent(Tag.class)) {
                Tag annTag = UserControllerSpec.class.getAnnotation(Tag.class);
                tags.add(new Tag().name(annTag.name()).description(annTag.description()));
            }
            openApi.tags(tags);
        };
    }

    // 解析ActionControllerSpec的注解,构建API定义
    private OpenApiCustomiser buildActionApi() {
        return openApi -> {
            openApi.info(new Info().title("操作日志API").version("v1.0"));

            Paths paths = new Paths();
            Method[] specMethods = ActionControllerSpec.class.getDeclaredMethods();
            for (Method method : specMethods) {
                Operation apiOperation = new Operation();
                
                if (method.isAnnotationPresent(Operation.class)) {
                    Operation ann = method.getAnnotation(Operation.class);
                    apiOperation.summary(ann.summary());
                    apiOperation.description(ann.description());
                }

                String path = "/api/actions/" + method.getName();
                paths.addPathItem(path, new PathItem().post(apiOperation));
            }
            openApi.paths(paths);

            List<Tag> tags = new ArrayList<>();
            if (ActionControllerSpec.class.isAnnotationPresent(Tag.class)) {
                Tag annTag = ActionControllerSpec.class.getAnnotation(Tag.class);
                tags.add(new Tag().name(annTag.name()).description(annTag.description()));
            }
            openApi.tags(tags);
        };
    }
}

方案2:让控制器实现Spec接口,自动继承注解

修改实际控制器,让它们实现rest-commons里的Spec接口,SpringDoc在扫描@RestController类时,会自动读取接口上的Swagger注解,这样不需要额外配置就能把接口的API定义关联到控制器上。

实现步骤:

  1. 修改rest1的UserController,实现UserControllerSpec:
@RestController
@RequestMapping("/api/users")
public class UserController implements UserControllerSpec {
    // 原有业务逻辑代码不变
}
  1. 修改rest2的ActionController,实现ActionControllerSpec:
@RestController
@RequestMapping("/api/actions")
public class ActionController implements ActionControllerSpec {
    // 原有业务逻辑代码不变
}
  1. 在rest1的Swagger配置中,扫描包含Spec接口和控制器的包:
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi combinedApiGroup() {
        return GroupedOpenApi.builder()
                .group("全模块API")
                // 扫描rest1的控制器包和rest-commons的Spec接口包
                .packagesToScan("com.yourproject.rest1", "com.yourproject.rest-commons")
                .build();
    }
}

注意:因为rest2部署在独立容器,rest1无法直接扫描到ActionController,但Spec接口在rest-commons里,SpringDoc会解析接口的注解生成API定义,同时你可以在rest1的配置中手动指定ActionControllerSpec.class到classesToScan中,确保接口被解析。

方案3:合并rest2的OpenAPI JSON

如果rest2本身也暴露了Swagger的OpenAPI接口(默认路径/v3/api-docs),可以在rest1中远程调用这个接口,获取JSON后合并到自己的Swagger-UI中。

实现步骤:

  1. 确保rest2启动后,rest1可以通过网络访问到http://rest2-host:port/v3/api-docs。
  2. 在rest1的配置中,添加OpenApiCustomiser来合并API定义:
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import io.swagger.v3.oas.models.OpenAPI;

@Configuration
public class SwaggerMergeConfig {

    private final RestTemplate restTemplate = new RestTemplate();

    @Bean
    public OpenApiCustomiser mergeRest2Api() {
        return openApi -> {
            try {
                // 调用rest2的API文档接口
                OpenAPI rest2OpenApi = restTemplate.getForObject(
                        "http://rest2-service:8080/v3/api-docs", 
                        OpenAPI.class
                );
                
                if (rest2OpenApi != null) {
                    // 合并路径、标签、组件定义
                    openApi.getPaths().putAll(rest2OpenApi.getPaths());
                    openApi.getTags().addAll(rest2OpenApi.getTags());
                    openApi.getComponents().addSchemas(rest2OpenApi.getComponents().getSchemas());
                }
            } catch (Exception e) {
                // 处理服务不可达的异常,可根据需求调整日志或降级逻辑
                e.printStackTrace();
            }
        };
    }
}

这个方案适合微服务架构下,跨服务合并API文档的场景。


内容的提问来源于stack exchange,提问作者phlipe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 03:22:03