Spring Gateway聚合Swagger在线文档

  • 为什么需要聚合?
  • 如何聚合?
    • 单个服务如何聚合Swagger?
      • 1.添加依赖
      • 2.基础配置类
      • 3.Swagger文档信息装配类
      • 4.微服务添加引用
      • 5.微服务添加配置
    • 网关如何聚合Swagger?
      • 1.添加依赖
      • 2.实现资源接口SwaggerResourcesProvider
      • 3.重写ApiResourceController接口类

为什么需要聚合?

  微服务模块众多,如果不聚合文档,则访问每个服务的API文档都需要单独访问一个Swagger UI界面,这么做客户端能否接受?
  既然使用了微服务,就应该有统一的API文档入口。

如何聚合?

统一的文档入口显然应该聚合到网关中,通过网关的入口统一映射到各个模块。

本文采用Spring Cloud Gateway 聚合 Swagger的方式生成API文档。

案例源码结构如下:
| spring-gateway-swagger
  |–gateway – 网关
  |–swagger-ui – swagger配置工程
  |–user-center – 用户服务
  |–order – 订单服务

单个服务如何聚合Swagger?

创建swagger配置工程

1.添加依赖

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><scope>provided</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><scope>provided</scope></dependency><!-- swagger ui --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId></dependency>

2.基础配置类

后续每个微服务依赖后即可通过配置控制文档属性

@Configuration
@ConfigurationProperties(prefix = "swagger")
public class SwaggerConfig {private String title;private String host;private String description;private String version;private String docs = "v2/api-docs";private boolean disable = false;private String basePackage = "com.gitee.xqxyxchy";private String termsOfServiceUrl = "https://gitee.com/xqxyxchy";private String contactName = "xqxyxchy";private String contactEmail = "xqxyxchy@126.com";private String contactUrl = "https://gitee.com/xqxyxchy";private Set<StringVendorExtension> stringExtensions = new HashSet<>();public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public String getDocs() {return docs;}public void setDocs(String docs) {this.docs = docs;}public boolean isDisable() {return disable;}public void setDisable(boolean disable) {this.disable = disable;}public String getBasePackage() {return basePackage;}public void setBasePackage(String basePackage) {this.basePackage = basePackage;}public String getTermsOfServiceUrl() {return termsOfServiceUrl;}public void setTermsOfServiceUrl(String termsOfServiceUrl) {this.termsOfServiceUrl = termsOfServiceUrl;}public String getContactName() {return contactName;}public void setContactName(String contactName) {this.contactName = contactName;}public String getContactEmail() {return contactEmail;}public void setContactEmail(String contactEmail) {this.contactEmail = contactEmail;}public String getContactUrl() {return contactUrl;}public void setContactUrl(String contactUrl) {this.contactUrl = contactUrl;}public Set<StringVendorExtension> getStringExtensions() {return stringExtensions;}public void setStringExtensions(Set<StringVendorExtension> stringExtensions) {this.stringExtensions = stringExtensions;}public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}@SuppressWarnings("rawtypes")public List<VendorExtension> getExtensions() {List<VendorExtension> result = new ArrayList<>();if(null == getStringExtensions()) {return result;}for(StringVendorExtension stringExtension : getStringExtensions()) {result.add(new springfox.documentation.service.StringVendorExtension(stringExtension.getName(), stringExtension.getValue()));}return result;}@Overridepublic String toString() {return "SwaggerConfig [disable=" + isDisable()  + ", host=" + getHost() + ", basePackage=" + getBasePackage() + "]";}}class StringVendorExtension implements VendorExtension<String> {private String name;private String value;public StringVendorExtension() {super();}public StringVendorExtension(String name, String value) {super();this.name = name;this.value = value;}public void setName(String name) {this.name = name;}public void setValue(String value) {this.value = value;}@Overridepublic String getName() {return name;}@Overridepublic String getValue() {return value;}}

3.Swagger文档信息装配类

支持多个基础包扫描
支持添加自定义参数

@Configuration
@EnableSwagger2
public class Swagger2Configuration extends WebMvcConfigurationSupport {private static Logger logger = LoggerFactory.getLogger(Swagger2Configuration.class);@Autowired@Lazyprivate SwaggerConfig swaggerConfig;@Beanpublic Docket createRestApi() {if(logger.isDebugEnabled()) {logger.debug("Starting Swagger");}logger.info("swaggerConfig ==> {}", swaggerConfig);StopWatch watch = new StopWatch();watch.start();Docket docket0 = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());String host = swaggerConfig.getHost();if(StringUtils.hasText(host)) {docket0.host(host);}ApiSelectorBuilder builder = docket0.select();builder.apis(basePackage(swaggerConfig.getBasePackage()));builder.apis(RequestHandlerSelectors.withClassAnnotation(Api.class));builder.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class));if(swaggerConfig.isDisable()){builder.paths(PathSelectors.none());}else{builder.paths(PathSelectors.any());}Docket docket = builder.build();watch.stop();if(logger.isDebugEnabled()) {logger.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());}return docket;}/*** Predicate that matches RequestHandler with given base package name for the class of the handler method.* This predicate includes all request handlers matching the provided basePackage** @param basePackage - base package of the classes* @return this*/public Predicate<RequestHandler> basePackage(final String basePackage) {return new Predicate<RequestHandler>() {@Overridepublic boolean apply(RequestHandler input) {return declaringClass(input).transform(handlerPackage(basePackage)).or(true);}};}/*** 处理包路径配置规则,支持多路径扫描匹配以逗号隔开* * @param basePackage 扫描包路径* @return Function*/private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {return new Function<Class<?>, Boolean>() {@Overridepublic Boolean apply(Class<?> input) {for (String strPackage : basePackage.split(",")) {if(Objects.isNull(input)) {continue;}if(Objects.isNull(input.getPackage())) {continue;}if(!StringUtils.hasText(strPackage)) {continue;}boolean isMatch = input.getPackage().getName().startsWith(strPackage);if (isMatch) {return true;}}return false;}};}/*** @param input RequestHandler* @return Optional*/@SuppressWarnings("deprecation")private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {return Optional.fromNullable(input.declaringClass());}@Beanpublic UiConfiguration uiConfig() {return UiConfigurationBuilder.builder().validatorUrl("").build();}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}private ApiInfo apiInfo() {ApiInfo info = new ApiInfoBuilder().extensions(swaggerConfig.getExtensions()).title(swaggerConfig.getTitle()).description(swaggerConfig.getDescription()).termsOfServiceUrl(swaggerConfig.getTermsOfServiceUrl()).version(swaggerConfig.getVersion()).contact(new Contact(swaggerConfig.getContactName(), swaggerConfig.getContactUrl(), swaggerConfig.getContactEmail())).build();return info;}@Value("${spring.jackson.date-format}")private String pattern;@Beanpublic LocalDateTimeSerializer localDateTimeSerializer() {return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));}}

4.微服务添加引用

 <dependency><groupId>com.gitee.xqxyxchy</groupId><artifactId>swagger-ui</artifactId></dependency>

5.微服务添加配置

swagger:title: 用户中心API文档description: 用户中心接口文档说明string-extensions:- name: apiPrefixvalue: /xqxyxchy/uc

至此单个服务的配置完成,此时我们可以验证一下,直接访问:http://127.0.0.1:5101/v2/api-docs,结果如下图:

网关如何聚合Swagger?

网关聚合的思想很简单,就是从路由中获取微服务的访问地址,然后拼接上 /v2/api-docs 即可。

1.添加依赖

     <!-- swagger ui --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId></dependency>

2.实现资源接口SwaggerResourcesProvider

生成聚合文档路由数据

@Component
@Primary
public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider {/*** swagger3默认的url后缀*/private static final String SWAGGER2URL = "/v2/api-docs";/*** 网关路由*/@Autowiredprivate RouteLocator routeLocator;/*** 网关应用名称*/@Value("${spring.application.name}")private String self;/*** 对于gateway来说这块比较重要 让swagger能找到对应的服务*/@Overridepublic List<SwaggerResource> get() {List<SwaggerResource> resources = new ArrayList<>();Map<String, String> routeHosts = Maps.newLinkedHashMap();// 获取所有可用的host:serviceIdrouteLocator.getRoutes().filter(route -> route.getUri().getHost() != null).filter(route -> !self.equals(route.getUri().getHost())).subscribe(route -> {Object routeObject = route.getMetadata().get("route");if(Objects.nonNull(routeObject)) {routeHosts.put(route.getUri().getHost(), routeObject.toString());}});// 记录已经添加过的serverSet<String> dealed = new HashSet<>();routeHosts.keySet().forEach(instance -> {String uri = routeHosts.get(instance);// 拼接urlString url = uri + SWAGGER2URL;if (!dealed.contains(url)) {dealed.add(url);SwaggerResource swaggerResource = new SwaggerResource();swaggerResource.setUrl(url);swaggerResource.setName(instance);resources.add(swaggerResource);}});return resources;}}

3.重写ApiResourceController接口类

spring gateway不支持webmvc,需要手动注入资源接口类

@Controller
@ApiIgnore
@RequestMapping("/swagger-resources")
public class GatewayApiResourceController {@Autowired(required = false)private SecurityConfiguration securityConfiguration;@Autowired(required = false)private UiConfiguration uiConfiguration;private final SwaggerResourcesProvider swaggerResources;@Autowiredpublic GatewayApiResourceController(SwaggerResourcesProvider swaggerResources) {this.swaggerResources = swaggerResources;}@RequestMapping(value = "/configuration/security")@ResponseBodypublic ResponseEntity<SecurityConfiguration> securityConfiguration() {return new ResponseEntity<SecurityConfiguration>(Optional.fromNullable(securityConfiguration).or(SecurityConfigurationBuilder.builder().build()),HttpStatus.OK);}@RequestMapping(value = "/configuration/ui")@ResponseBodypublic ResponseEntity<UiConfiguration> uiConfiguration() {return new ResponseEntity<UiConfiguration>(Optional.fromNullable(uiConfiguration).or(UiConfigurationBuilder.builder().build()), HttpStatus.OK);}@RequestMapping@ResponseBodypublic ResponseEntity<List<SwaggerResource>> swaggerResources() {return new ResponseEntity<List<SwaggerResource>>(swaggerResources.get(), HttpStatus.OK);}}

网关的配置这里就完成,此时启动网关、用户、订单服务,直接访问网关的文档:http://localhost:5100/doc.html,结果如下图:

相关源码已上传gitee SpringGateway聚合Swagger接口文档

Spring Gateway聚合Swagger在线文档相关推荐

  1. sentinel 官方文档_SpringCloud网关聚合Swagger接口文档实践

    目前大多数项目都是以微服务架构设计,以前后端分离技术解耦前端开发工程师和后端开发工程师的工作量,这样一来前后端的对接将是一项重要的沟通工作量,如果后端没有一个合适的API文档,那么这样的前后端对接将是 ...

  2. 正则提取Swagger在线文档里面的返回实体类字段

    说明:由于此问题也是关于数据正则提取的,但其实和Python没有什么关联,为了方便整理特此放在了该目录下面 通过swagger在线文档的/openapi.json地址,可以看到其返回内容是一个json ...

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

    前言: 在微服务系统中,通常每个服务都会暴露其接口文档,在前端人员或测试人员查看的时候,并不是那么方便,我们需要告诉相关人员每个服务的文档地址,由于swagger/knif4j(knif4j为更易用的 ...

  4. 聚合微服务中的 Swagger API 文档

    没有做 API 文档聚合,访问每个服务的 API 文档都需要访问单独的 swagger-ui.html 页面,既然我们使用了微服务,就应该有统一的 API 文档入口,而 knife4j 有这方面的支持 ...

  5. Swagger接口在线文档

    1.Swagger是什么? Swagger围绕OAS构建RESTFUL文档: Swagger动态生成接口定义文档: Swagger易用免费且开源: Swagger就是将项目中所有的接口展现在页面上,并 ...

  6. swagger api文档_带有Swagger的Spring Rest API –创建文档

    swagger api文档 使REST API易于使用的真正关键是好的文档. 但是,即使您的文档做得很好,您也需要设置公司流程的权利以正确,及时地发布它. 确保利益相关者按时收到是一回事,但是您也要负 ...

  7. swagger api文档_带有Swagger的Spring Rest API –公开文档

    swagger api文档 创建API文档后,将其提供给涉众很重要. 在理想情况下,此发布的文档将足够灵活以解决任何最后的更改,并且易于分发(就成本以及完成此操作所需的时间而言). 为了使之成为可能, ...

  8. semantic ui中文文档_求你别再用swagger了,给你推荐几个在线文档生成神器

    开局先推荐: Java面试刷题网站​www.javazhiyin.com 前言 最近公司打算做一个openapi开放平台,让我找一款好用的在线文档生成工具,具体要求如下: 必须是开源的 能够实时生成在 ...

  9. Spring Boot(9)之 Swagger 接口文档生成器

    1.添加依赖 spring4all工具源码 <dependency><groupId>com.spring4all</groupId><artifactId& ...

最新文章

  1. spring-cloud-ribbon负载均衡
  2. 程序员单身比例有多高?【2019开发者图鉴】告诉你
  3. 无连接的用户数据报协议UDP
  4. boost::callable_traits添加可变参数的测试程序
  5. 风雨网规路:跌倒了,是件坏事吗?
  6. thinkphp日志泄漏漏洞_ThinkPHP框架任意代码执行漏洞的利用及其修复方法
  7. 【Hexo】deploy出错的解决方法
  8. python 提升工作效率_Python那些事——5个提升生产效率的Python开发和配置的小技巧...
  9. 自学Java的人,如何系统全面的学习?
  10. java通过InputStream读取文件
  11. 动画演绎Java常用数据结构
  12. 【转】 STM32 入门教程 系统时钟 SysTick
  13. 什么是DNS智能云解析,什么是NS,更换NS多久生效?
  14. Tortoise 没有显示绿色图标
  15. Hibernate使用手册(官网)
  16. RK3588 Android12开机log分析
  17. SAP ERP FI(Financial Accounting)财务会计--BW方向--初级--1
  18. 命令行把java项目打成jar包
  19. FPGA 结构分析 -IO 资源
  20. java虚拟机的CAS_Java虚拟机学习(二)

热门文章

  1. PYNQ-Z1上手详细教程
  2. 解决Windows因D3DCompiler_47.dll文件找不到等问题
  3. opencv十四天入门学习——task2
  4. 基于瑞芯微3399的嵌入式linux,瑞芯微x3399 linux QT平台WIFI移植详解
  5. 安装Oracle12C提示Hard limit:maxinum open file descriptors的解决方法
  6. GitLab CI/CD 自动化构建与发布实践
  7. mui ajax请求异常类型abort
  8. 计算机专业可以考什么证书?
  9. objectArx ---块表及图块
  10. Open DDS的那些坑