有人在gateway处做了文档聚合,它的聚合模式如下图所示
它的原理很简单,就是将请求转发到微服务,从微服务的restful接口中获取swagger的json信息,然后通过前端将swagger信息渲染出来。这样做的好处就是只需要在网关处集成swagger-ui,其它微服务不需要再单独集成swagger-ui,只需要收集swagger信息然后暴露接口给gateway,等着gateway来取信息即可。但是它没有完全解决上面提到的问题,而且还引入了新的问题
针对这个问题,我想了想,使用另外一种方式尝试着进行改造。
好吧,这个名字我瞎起的。具体技术架构如下图所示
系统流程如下:
需要注意的是swagger注册中心只部署开发环境或者公司局域网环境,我们公司局域网能直接访问开发环境。
集中注册模式的代码设计如下,这里搞两个单独的项目
在一切开始之前,需要了解下swagger-ui的实现原理
正如之前所说,swagger-spring-boot-starter是客户端组件,微服务客户端使用封装好的该组件扫描项目中的swagger信息并上传到swagger注册中心。
关键的技术点是如何手动扫描项目的swagger信息,只要能拿到swagger信息,无论使用什么方式上传到swagger注册中心都很简单了。关于这个技术点想了一会儿没想到好办法,只能去看源代码,看了一会儿觉得云里雾里的,最终突然灵光一闪,swagger-ui的实现给了我灵感。
这段代码详细讲解了如何获取Swagger对象,这给我的实现提供了很大的参考依据。
可以看到,改接口仅仅是调用了swaggerResource的get方法,然后就直接返回了,那就再看看swaggerResource是什么东西
它只是个接口,那它的实现类呢,它的实现类只有一个,就是InMemorySwaggerResourcesProvider类
它的GET方法是这样子的
看到这里我不禁陷入了思考,难道要给documentationCache手动填充文档。。但是看这个名字就知道是基于内存的东西,要维护CRUD状态似乎有点麻烦。。看看这个代码是咋写的
确实是基于内存的东西,但是值提供了add方法,没提供remove方法,那获取到documentionLookup对象之后手动移除呢?仔细看看all()方法
它被Collections工具类包装成了不可修改的了,那手动移除的方式就没戏了。
换一种思路,其实还有另外一种方法,重新实现SwaggerResourcesProvider接口,并将实现类使用@Primary注解修饰,覆盖默认的InMemorySwaggerResourcesProvider实现类,重写get()方法即可,那这时候的自由度就大了去了,这里可以直接使用从数据库读的方式获取所有的group。
/swagger-resources接口的返回值是List类型,SwaggerResource类的定义如下
它是一个swagger注册中心,对swagger文档进行持久化并进行CRUD操作,最终在knife4j中展示。它应当包含如下功能
设计上,用两张表分别存储group信息和文档详情信息
CREATETABLE`group_info`(`id`int(11)NOTNULLAUTO_INCREMENTCOMMENT'自增主键',`name`varchar(64)NOTNULLCOMMENT'groupName',`location`varchar(128)NOTNULLCOMMENT'location',`version`varchar(16)NOTNULLCOMMENT'version',`url`varchar(128)NOTNULLCOMMENT'url',`app_name`varchar(64)DEFAULTNULLCOMMENT'服务名(spring.application.name)',`gateway`varchar(64)DEFAULTNULLCOMMENT'网关,无则不填',PRIMARYKEY(`id`),UNIQUEKEY`group_info_name`(`name`)COMMENT'groupname唯一',UNIQUEKEY`group_info_app_name`(`app_name`)COMMENT'appname唯一')ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;CREATETABLE`swagger_json`(`id`int(11)NOTNULLAUTO_INCREMENT,`group_name`varchar(64)NOTNULL,`content`longtextNOTNULLCOMMENT'swagger具体信息',PRIMARYKEY(`id`),UNIQUEKEY`swagger_json_groupname`(`group_name`)COMMENT'groupName唯一')ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;group_info表用于存储swagger的group信息,/swagger-resources接口将会从该表中取group数据
swagger_json表用于存储swagger的原始信息,用于文档渲染。
对应以上两个表,注册接口有两个实体类。注册逻辑是:存在则更新,不存在就新增,groupName和appName都要保持唯一。
默认值是/v2/api-docs,但是可以自定义,这里要求客户端在注册的时候就约定好接口路径是/swagger/detail
该接口从数据库中获取swagger信息。
从之前的/swagger-resources源码分析过,想要从数据库自定义获取group列表,就需要重新实现SwaggerResourcesProvider接口并且标记为@Primary
设计上,要求做到微服务客户端只需要引入组件jar包,然后配置文件配置一些swagger的基本信息,服务启动之后就能自动上传swagger文档到swagger注册中心,具体技术细节,应当包含如下功能
上面分析过/v2/api-docs的实现原理,利用它的实现原理,可以轻松获取到Swagger对象
上传的话,根据配置文件中是否配置serverUrl决定采用服务发现方式还是直接请求方式上传Swagger信息
这个非常简单,在resources/META-INF目录下新建文件
并配置好即可。
swagger-spring-boot-starter不依赖nacosclient或者eurkaclient,而是依赖了它们的公共接口模块spring-cloud-commons,实际上nacosclient或者eurekaclient均是该模块的具体实现,所以swagger-spring-boot-starter可以兼容两种客户端服务发现组件的实现,但是服务端因为具体依赖了某种服务发现组件,在我这里默认使用nacos,如果要用eureka需要自行改造。
该项目启动需要连接mysql数据库以及nacos。
准备好外部依赖之后,执行sql文件夹中的sql文件,最后启动项目即可,启动成功之后,访问项目的/doc.html,即可看到knife4j的文档页面。
上一步启动好了swagger-register-server,接下来需要打包swagger-spring-boot-starter已提供微服务客户端使用。
因为这里并没有上传maven中央仓库,所以有条件的可以上传nexus私服,没条件的可以直接运行命令mvncleaninstall将jar包安装到本地maven仓库以便使用。
可以使用intelij自带的工具初始化一个springboot的项目,这里使用了2.3.4.REALEASE版本的springboot版本号(经过测试发现,nacos版本号过高会导致服务发现功能故障,版本号低一些程序功能会更稳定)。
还有swagger注册中心地址
由于受限于资源和网络带宽,访问速度会比较慢;请善待公共资源,不要对它们进行压测和其它非正常操作。
配置文件中有个配置项:swagger.config.server-url,若该配置项不为空,则走直连模式,即不通过服务发现直接请求该server-url上传swagger文档;
如果未配置该配置项,则检查swagger.config.service-id字段,如果该字段也没有配置值,则报错并跳过swagger文档上传。