Springboot3基于SpringDoc实现接口分组功能

问题

最近在接触SpringBoot3,整合Swagger文档组件的过程中发现一个问题,通过springdoc.group-configs[0].display-name=示例接口配置分组名称在界面上面还是显示OpenAPI definition,如下图所示:

查询了下官方文档,可以通过注入GroupedOpenApi对象实现接口分组描述信息展示,同时找到了一个issues提供了一种硬编码的实现方式,但是硬编码的实现方式不利于后期接口描述信息的问题,理想方式还是在配置文件中进行维护,随后查阅了SpringDoc底层的AutoConfiguration实现逻辑,并没有找到相关配置项,所以开始动手改造,增加相关的配置项,然后动态注入GroupedOpenApi

解决思路

自定义GroupConfig配置类:

public class CustomerGroupConfig extends SpringDocConfigProperties.GroupConfig {

    private Info apiInfo;

    public CustomerGroupConfig() {
    }

    public CustomerGroupConfig(String group, List<String> pathsToMatch, List<String> packagesToScan, List<String> packagesToExclude, List<String> pathsToExclude, List<String> producesToMatch, List<String> consumesToMatch, List<String> headersToMatch, String displayName, Info apiInfo) {
        super(group, pathsToMatch, packagesToScan, packagesToExclude, pathsToExclude, producesToMatch, consumesToMatch, headersToMatch, displayName);
        this.apiInfo = apiInfo;
    }

    public Info getApiInfo() {
        return apiInfo;
    }

    public CustomerGroupConfig setApiInfo(Info apiInfo) {
        this.apiInfo = apiInfo;
        return this;
    }
}
public class CustomSpringDocConfigProperties {

    private Set<CustomerGroupConfig> groupConfigs = new HashSet();

    public CustomSpringDocConfigProperties() {
    }

    public CustomSpringDocConfigProperties(Set<CustomerGroupConfig> groupConfigs) {
        this.groupConfigs = groupConfigs;
    }

    public Set<CustomerGroupConfig> getGroupConfigs() {
        return groupConfigs;
    }

    public Set<SpringDocConfigProperties.GroupConfig> getOriginGroupConfigs() {
        return groupConfigs.stream().map(this::convert).collect(Collectors.toSet());
    }

    public SpringDocConfigProperties.GroupConfig convert(CustomerGroupConfig config) {
        SpringDocConfigProperties.GroupConfig groupConfig = new SpringDocConfigProperties.GroupConfig();
        groupConfig.setGroup(config.getGroup());
        groupConfig.setDisplayName(config.getDisplayName());
        groupConfig.setPackagesToScan(config.getPackagesToScan());
        return groupConfig;
    }

}

通过BeanDefinitionRegistryPostProcessor向Spring容器中动态注入GroupedOpenApi接口分组对象,部分代码参考了源码类SpringdocBeanFactoryConfigurer

@Configuration
@ConditionalOnProperty(
        name = {"springdoc.api-docs.enabled"},
        matchIfMissing = true
)
@ConditionalOnWebApplication
public class DynamicSpringDocGroupConfiguration implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {

    private static final String CONFIG_PRO_PREFIX = "springdoc.custom";

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        final DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;

        final BindResult<CustomSpringDocConfigProperties> result = Binder.get(environment)
                .bind(CONFIG_PRO_PREFIX, CustomSpringDocConfigProperties.class);

        if (result.isBound()) {
            List<GroupedOpenApi> groupedOpenApis = result.get().getGroupConfigs().stream()
                    .map(elt -> {
                        GroupedOpenApi.Builder builder = GroupedOpenApi.builder();
                        if (!CollectionUtils.isEmpty(elt.getPackagesToScan()))
                            builder.packagesToScan(elt.getPackagesToScan().toArray(new String[0]));
                        if (!CollectionUtils.isEmpty(elt.getPathsToMatch()))
                            builder.pathsToMatch(elt.getPathsToMatch().toArray(new String[0]));
                        if (!CollectionUtils.isEmpty(elt.getProducesToMatch()))
                            builder.producesToMatch(elt.getProducesToMatch().toArray(new String[0]));
                        if (!CollectionUtils.isEmpty(elt.getConsumesToMatch()))
                            builder.consumesToMatch(elt.getConsumesToMatch().toArray(new String[0]));
                        if (!CollectionUtils.isEmpty(elt.getHeadersToMatch()))
                            builder.headersToMatch(elt.getHeadersToMatch().toArray(new String[0]));
                        if (!CollectionUtils.isEmpty(elt.getPathsToExclude()))
                            builder.pathsToExclude(elt.getPathsToExclude().toArray(new String[0]));
                        if (!CollectionUtils.isEmpty(elt.getPackagesToExclude()))
                            builder.packagesToExclude(elt.getPackagesToExclude().toArray(new String[0]));
                        if (StringUtils.isNotEmpty(elt.getDisplayName()))
                            builder.displayName(elt.getDisplayName());

                        if (Optional.ofNullable(elt.getApiInfo()).isPresent()) {
                            builder.addOpenApiCustomizer(openApi -> openApi.info(elt.getApiInfo()));
                        }

                        return builder
                                .group(elt.getGroup())
                                .build();
                    })
                    .toList();


            groupedOpenApis.forEach(elt -> {
                BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(GroupedOpenApi.class, () -> elt).getBeanDefinition();
                defaultListableBeanFactory.registerBeanDefinition(String.format("%sGroupedOpenApi", elt.getGroup()), beanDefinition);
            });

        }

    }

}

增加配置项,用于@Conditional(CacheOrGroupedOpenApiCondition.class)条件成立,注入MultipleOpenApiSupportConfiguration等配置类:

springdoc.show-actuator=true

接口分组配置如下:

springdoc.custom.group-configs[0].group=demo
springdoc.custom.group-configs[0].display-name=示例接口
springdoc.custom.group-configs[0].packages-to-scan[0]=cn.codest.server.common
springdoc.custom.group-configs[0].api-info.title=示例接口
springdoc.custom.group-configs[0].api-info.description=示例接口清单
springdoc.custom.group-configs[0].api-info.version=v1.0
springdoc.custom.group-configs[0].api-info.termsOfService=http://localhost:8080/
springdoc.custom.group-configs[0].api-info.license.name=Apache 2.0
springdoc.custom.group-configs[1].group=user
springdoc.custom.group-configs[1].display-name=用户管理接口
springdoc.custom.group-configs[1].packages-to-scan[0]=cn.codest.server.user
springdoc.custom.group-configs[1].api-info.title=用户管理
springdoc.custom.group-configs[1].api-info.description=用户管理接口清单
springdoc.custom.group-configs[1].api-info.version=v1.0
springdoc.custom.group-configs[1].api-info.termsOfService=http://localhost:8080/
springdoc.custom.group-configs[1].api-info.license.name=Apache 2.0

效果如下:

后面有空再GitHub提交一个PR给SpringDoc。

posted @ 2024-03-26 11:40  codest  阅读(152)  评论(0编辑  收藏  举报