SpringCloud Gateway网关统一聚合Swagger接口文档(knife4j),实现通过网关统一文档地址查看所有子服务的接口文档

该博客介绍了如何在Spring Cloud Gateway中利用Knife4j聚合微服务的Swagger文档,提供统一的访问入口。通过配置文件、定制配置类和资源访问控制器,实现微服务接口文档的集中展示。访问聚合后的文档地址http://localhost:2000/doc.html,简化了前端和测试人员查看接口文档的步骤。
摘要由CSDN通过智能技术生成

前言:

在微服务系统中,通常每个服务都会暴露其接口文档,在前端人员或测试人员查看的时候,并不是那么方便,我们需要告诉相关人员每个服务的文档地址,由于swagger/knif4j(knif4j为更易用的swagger封装,点此了解knif4j)支持以rest的方式获取所有微服务的文档数据,再配合Gateway的路由,我们可以轻松的通过其相关API做到聚合文档的效果,做到只提供一个文档地址,便可查看所有微服务下的接口文档

注意:

  • 各微服务的knife4j配置及文档访问,观阅此文前默认认为你已经是完成且可访问的,如未完成或遇到问题等,参考knif4j官网按照文档进行操作,或在评论区留言。

  • 如果想方便简单的接入,可直接参考、套用我的代码,之前写过一篇文章为Nacos结合Gateway实现动态路由,本文示例代码仍基于其仓库进行编写,仓库地址:springcloud-nacos-example tree:knif4j
    点个关注,来个star,甚是感激 😃

  • 以下操作全程在Gateway模块,各微服务模块如果Docket的Group类型(API分组,比如openApi,adminApi,feignApi等)定义统一,则无需改变

一、依赖

Gateway聚合文档服务,需要knif4j的依赖:

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

本例使用的版本为2.0.9,其他版本的请自行结合并尝试

二、配置

1).自定义配置文件
在resources下新建:swagger-faced.yml(也可放在配置中心,按个人所需)
此配置文件也可以用.properties代替,不过为了其内容具有结构性,此处采用了yaml配置
配置各API分组下,分别有哪些服务需要暴露:

swagger:
  faced:
    open:
      groupName: 'OPEN前端'
      groupUrl: 'group=openApi'
      applications:
        - server1-application
        - server2-application

    admin:
      groupName: 'ADMIN后台'
      groupUrl: 'group=adminApi'
      applications:
        - server1-application
        - server2-application

微服务中配置的Docket的Group,需跟此处swagger.faced.**的key一致,
例子中两个分组:open,admin,那么微服务中也同样有两个分组(或其中之一,详情可看示例代码)

属性解释:
groupName: 定义了API分组的前缀名称,后面代码中使用“【】”括了起来使其更直观
groupUrl: 可以理解为访问doc需要的kv参数,此参数将被拼接到v2/api-docs?(swagger文档数据获取接口,GET类型)后进行使用
applications: 属性(list)配置了各分组下有哪些服务需要暴露,可以按需合理的进行暴露

2).SwaggerFacedBundleConfig配置(属性配置):
SwaggerFacedBundleConfig将自定义的yaml配置注入到spring环境中,以便在swaggerResourceProvider中访问配置:

@Configuration
class SwaggerFacedBundleConfig {

    private val swaggerFacedBundleName = "swagger-faced.yml"

    @Bean
    fun swaggerFacedProperties(): PropertySourcesPlaceholderConfigurer? {
        val configurer = PropertySourcesPlaceholderConfigurer()
        val yaml = YamlPropertiesFactoryBean()
        yaml.setResources(ClassPathResource(swaggerFacedBundleName))
        configurer.setProperties(yaml.getObject()!!)
        return configurer
    }

    @Bean
    @ConfigurationProperties(prefix = "swagger.faced.open")
    fun open() = SwaggerFacedItem()

    @Bean
    @ConfigurationProperties(prefix = "swagger.faced.admin")
    fun admin() = SwaggerFacedItem()

}

open class SwaggerFacedItem {
    open lateinit var groupName: String
    open lateinit var groupUrl: String
    open lateinit var applications: List<String>
}

3).swaggerResourceProvider配置(实际聚合配置):
此配置类利用Gateway的路由列表以及上述配置,对各微服务API文档的resource聚合进行提供

@Component
@Primary
class SwaggerResourceConfig : SwaggerResourcesProvider {

    val log = LoggerFactory.getLogger(SwaggerResourceConfig::class.java)

    @Autowired
    lateinit var swaggerFaced: List<SwaggerFacedItem>

    override fun get(): List<SwaggerResource> {
        val resources = mutableListOf<SwaggerResource>()
        NacosRouteDefinitionRepository.routeDefinitions.stream().forEach { route: RouteDefinition? ->
            route!!.predicates.stream()
                .filter { predicateDefinition: PredicateDefinition ->
                    "Path".equals(
                        predicateDefinition.name,
                        ignoreCase = true
                    )
                }
                .forEach { predicateDefinition: PredicateDefinition ->
                    addResource(
                        resources,
                        route,
                        predicateDefinition
                    )
                }
        }
        return resources.sortedBy { it.name }
    }

    private fun addResource(
        resources: MutableList<SwaggerResource>,
        route: RouteDefinition?,
        predicateDefinition: PredicateDefinition
    ) {
        for (facedItem in swaggerFaced) {
            facedItem.applications.filter { route!!.id.equals(it, ignoreCase = true) }.forEach { _ ->
                resources.add(
                    swaggerResource(
                        "【${facedItem.groupName}】" + route!!.id,//中括号更直观
                        predicateDefinition.args[NAME_PREFIX]!!
                            .replace("**", SWAGGER_DOC_PATH + facedItem.groupUrl)
                    )
                )
            }
        }
    }

    private fun swaggerResource(name: String, location: String): SwaggerResource {
        log.debug("name:{},location:{}", name, location)
        return SwaggerResource().apply {
            this.name = name
            this.location = location
            this.swaggerVersion = "2.0"
        }
    }

    companion object {
        private const val SWAGGER_DOC_PATH = "v2/api-docs?"
        private const val NAME_PREFIX = "pattern"
    }
}

其中NacosRouteDefinitionRepository.routeDefinitions为当前的provider提供了各微服务的路由信息列表,也可以根据项目实际情况替换为其他的微服务路由信息列表

三、定义资源访问控制器

暴露聚合文档的资源访问接口,此控制器的接口,在聚合文档的页面打开时被调用

/**
 * 自定义Swagger的各个配置节点
 * Created by macro on 2020/7/9.
 */
@RestController
class SwaggerHandler {

    @Autowired
    lateinit var swaggerResources: SwaggerResourcesProvider

    @Autowired(required = false)
    lateinit var securityConfiguration: SecurityConfiguration

    @Autowired(required = false)
    lateinit var uiConfiguration: UiConfiguration

    /**
     * Swagger安全配置,支持oauth和apiKey设置
     */
    @GetMapping("/swagger-resources/configuration/security")
    fun securityConfiguration(): Mono<ResponseEntity<SecurityConfiguration>> {
        return Mono.just(
            ResponseEntity(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
                HttpStatus.OK
            )
        )
    }

    /**
     * Swagger UI配置
     */
    @GetMapping("/swagger-resources/configuration/ui")
    fun uiConfiguration(): Mono<ResponseEntity<UiConfiguration>> {
        return Mono.just(
            ResponseEntity(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK
            )
        )
    }

    /**
     * Swagger资源配置,微服务中这各个服务的api-docs信息
     */
    @GetMapping("/swagger-resources")
    fun swaggerResources(): Mono<ResponseEntity<*>> {
        return Mono.just(ResponseEntity(swaggerResources.get(), HttpStatus.OK))
    }
}

至此聚合完成,是不是很简单?一个配置文件 两个配置类 一个controller

启动各微服务和gateway服务,可以通过:gateway访问地址/doc.html 来访问已经聚合的文档,本例中访问:http://localhost:2000/doc.html,即可看到聚合后的接口文档

四、效果对比

以我编写的实际例子来对比效果:
gateway的端口为2000,server1-application的端口为4001

未聚合文档前需要这样访问(含有具体微服务的前缀或端口):
http://localhost:2000/server1-application/doc.html
或:
http://localhost:4001/doc.html

效果是这样的:
注意看左上角
在这里插入图片描述
聚合文档后,统一访问(无需微服务的前缀或具体端口):
http://localhost:2000/doc.html
无需再访问微服务各自文档接口
在这里插入图片描述
另外这个组别(provider中get()方法的返回值:List<SwaggerResource>)可以自定义排序规则,当前实现默认使用了名称的自然顺序作为排序方式:

resources.sortedBy { it.name }

五、小结

以网关聚合的方式访问文档,对于比较依赖文档的人员,还是非常便捷的
不过比较遗憾的是不支持跨分组搜索,有兴趣的话可以拓展一下搜索相关的源码,这样就更方便了
聚合步骤也还是比较简单的,只需要跟文中操作步骤来就可以实现了
本文示例的代码地址:
springcloud-nacos-example tree:knif4j

欢迎在留言区发表你的看法意见与建议,共同探讨~~

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值