目录

  • 一、简介
    • 1.1、maven依赖
  • 二、核心配置类
    • 2.1、SwaggerUI配置
    • 2.2、服务定义的上下文
    • 2.3、定时刷下文档定义
    • 2.4、文档接口
  • 三、配置
    • 3.1、主类
    • 3.2、bootstrap.properties
    • 3.3、文档中心相关的数据库配置
  • 四、效果
    • 4.1、系统说明
    • 4.2、注册中心效果图
    • 4.3、nginx配置
    • 4.4、文档中心效果图

一、简介

  根据我们之前的工程已经实现了自动生成文档了,可以根据服务的文档地址进行查阅,但是如果服务很多,比如好几十个系统,那么就是一个很麻烦的事情了,所以我们的文档中心应用而生,那就是通过一个文档服务可以管理所有应用的文档,可以查看到所有服务的文档,是不是很方便?那我们就来看看怎么实现吧。

1.1、maven依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.alian.microservice</groupId><artifactId>parent</artifactId><version>1.0.0-SNAPSHOT</version></parent><artifactId>doc-service</artifactId><version>1.0.0-SNAPSHOT</version><name>doc-service</name><description>文档服务</description><dependencies><!--公共api(整合了swagger)--><dependency><groupId>cn.alian.microservice</groupId><artifactId>common-api</artifactId><version>1.0.0-SNAPSHOT</version></dependency><!--配置中心--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><!--注册中心--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!--压缩打包--><dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId></dependency><!--字符处理--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId></dependency></dependencies></project>

二、核心配置类

2.1、SwaggerUI配置

SwaggerUIConfiguration.java

package cn.alian.microservice.doc.config;import cn.alian.microservice.doc.definition.ServiceDefinitionsContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.web.client.RestTemplate;
import springfox.documentation.swagger.web.InMemorySwaggerResourcesProvider;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;import java.util.List;@Configuration
public class SwaggerUIConfiguration {@Autowiredprivate ServiceDefinitionsContext definitionContext;@Beanpublic RestTemplate configureTemplate() {return new RestTemplate();}@Primary@Bean@Lazypublic SwaggerResourcesProvider swaggerResourcesProvider(InMemorySwaggerResourcesProvider defaultResourcesProvider) {return () -> {List<SwaggerResource> swaggerDefinitions = this.definitionContext.getSwaggerDefinitions();return !swaggerDefinitions.isEmpty() ? swaggerDefinitions : defaultResourcesProvider.get();};}
}

  这里有两个配置

  • 向容器注册RestTemplate,后续可以通过他发起请求
  • 自定义实现了SwaggerResourcesProvider接口,返回文档定义列表(具体的实现后面会讲到)

2.2、服务定义的上下文

ServiceDefinitionsContext.java

package cn.alian.microservice.doc.definition;import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;@Component
@Scope(scopeName = "singleton")
public class ServiceDefinitionsContext {@Value("${swagger.config.multi-instance:true}")private boolean multiInstance;private final ConcurrentHashMap<String, Pair<String, String>> serviceDescriptions = new ConcurrentHashMap<>();public void addServiceDefinition(String serviceName, String serviceDescription, ServiceInstance instance) {if (this.multiInstance) {//多实例时服务描述:服务名-host-portthis.serviceDescriptions.put(serviceName + "-" + instance.getHost() + "-" + instance.getPort(), Pair.of(serviceName, serviceDescription));} else {this.serviceDescriptions.put(serviceName, Pair.of(serviceName, serviceDescription));}}public String getSwaggerDefinition(String serviceId) {return this.serviceDescriptions.get(serviceId).getRight();}public List<SwaggerResource> getSwaggerDefinitions() {return this.serviceDescriptions.keySet().stream().sorted().map(k -> {SwaggerResource resource = new SwaggerResource();resource.setLocation("/service/" + k);resource.setName(k);resource.setSwaggerVersion("2.0");return resource;}).collect(Collectors.toList());}public void clear() {this.serviceDescriptions.clear();}
}
  • 此处有一个添加服务描述信息的方法,可以支持多实例,就相当于所有服务的文档定义要素都会存储到一个ConcurrentHashMap中
  • 遍历我们的服务描述信息,即可得到SwaggerResourcesProvider需要的SwaggerResource列表
  • 提供可以单独获取某个服务的文档定义的方法(getSwaggerDefinition)
  • 清空ConcurrentHashMap能达到刷新文档定义缓存的作用

  关键在于ConcurrentHashMap的数据怎么来呢?接下来继续说…

2.3、定时刷下文档定义

ServiceDescriptionUpdater.java

package cn.alian.microservice.doc.definition;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;import java.time.Duration;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;@Slf4j
@Component
public class ServiceDescriptionUpdater {private static final String DEFAULT_SWAGGER_URL = "/v2/api-docs";@Autowiredprivate DiscoveryClient discoveryClient;@Autowiredprivate ServiceDefinitionsContext definitionContext;private final RestTemplate template = (new RestTemplateBuilder(new org.springframework.boot.web.client.RestTemplateCustomizer[0])).setConnectTimeout(Duration.ofSeconds(3L)).setReadTimeout(Duration.ofSeconds(3L)).build();@Scheduled(fixedDelayString = "${swagger.config.refresh-rate}")public void refreshSwaggerConfigurations() {log.info("开始刷下文档定义");this.discoveryClient.getServices().forEach(serviceId -> {log.info("根据服务名获取服务的实例信息 : {} ", serviceId);List<ServiceInstance> serviceInstances = this.discoveryClient.getInstances(serviceId);if (serviceInstances == null || serviceInstances.isEmpty()) {log.info("没有找到服务的实例信息 : {} ", serviceId);} else {for (ServiceInstance instance : serviceInstances) {String swaggerURL = getSwaggerURL(instance);Optional<Object> jsonData = getSwaggerDefinitionForAPI(serviceId, swaggerURL);if (jsonData.isPresent()) {String content = getJSON(serviceId, jsonData.get());this.definitionContext.addServiceDefinition(serviceId, content, instance);} else {log.error("未获取到swagger定义json : {}", serviceId);}log.info("文档定义刷新完成时间:  {}", LocalDate.now());}}});}private String getSwaggerURL(ServiceInstance instance) {Map<String, String> metadata = instance.getMetadata();String contextPath = metadata.get("contextPath");String url = String.valueOf(instance.getUri());if (StringUtils.isNotBlank(contextPath) && !contextPath.contains("${server.servlet.context-path}")) {url = url + contextPath;}return url + DEFAULT_SWAGGER_URL;}private Optional<Object> getSwaggerDefinitionForAPI(String serviceName, String url) {log.debug("获取swagger定义的api数据 : {} : URL : {} ", serviceName, url);try {Object jsonData = this.template.getForObject(url, Object.class, new Object[0]);assert jsonData != null;return Optional.of(jsonData);} catch (RestClientException ex) {log.error("获取swagger定义的api数据异常 : {} Error : {} ", serviceName, ex.getMessage());return Optional.empty();}}public String getJSON(String serviceId, Object jsonData) {try {return (new ObjectMapper()).writeValueAsString(jsonData);} catch (JsonProcessingException e) {log.error("实例【{}】json异常,Error : {} ", serviceId, e.getMessage());return "";}}}

  实现步骤如下:

  • 在定时任务中,通过DiscoveryClient可以查找到所有的服务名列表,比如(order,stock等)
  • 遍历查找到的服务名列表,根据服务名去查找服务的实例对象列表,为什么是列表呢?因为一个服务可能有多个实例,比如订单系统部署了多个实例
  • 继续遍历实例对象列表,通过实例对象的元数据等相关信息,我们可以获取到实例的swagger文档地址
  • 根据服务名及获取到的swagger文档地址,然后通过之前的RestTemplate,获取到swagger定义的api数据 ,从而将其转化为json数据,并且将服务名、实例、以及api的json数据存到ConcurrentHashMap中

2.4、文档接口

DocController.java

package cn.alian.microservice.doc.controller;import cn.alian.microservice.doc.definition.ServiceDefinitionsContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@Api(description = "文档生成工具服务")
@RestController
public class DocController {@Autowiredprivate ServiceDefinitionsContext definitionContext;@ApiOperation("获取文档定义")@GetMapping({"/service/{serviceName}"})public String getServiceDefinition(@PathVariable("serviceName") String serviceName) {return this.definitionContext.getSwaggerDefinition(serviceName);}@ApiOperation("重新加载文档")@GetMapping({"/reload"})public void reload() {log.info("重新加载文档");this.definitionContext.clear();}
}

  两个简单接口不过多解释了。

三、配置

3.1、主类

DocServiceApplication.java

package cn.alian.microservice.doc;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.scheduling.annotation.EnableScheduling;@EnableScheduling
@EnableDiscoveryClient
@SpringBootApplication
public class DocServiceApplication {public static void main(String[] args) {SpringApplication.run(DocServiceApplication.class, args);}}

  两个关键的注解

  • @EnableScheduling 是为了使用定时任务实现文档刷新
  • @EnableDiscoveryClient 是作为客户端包括注册中心和配置中心

3.2、bootstrap.properties

#应用名
spring.application.name=doc-service
#开发环境
spring.profiles.active=dev
#配置标签(根据配置中心,本文全是采用master)
spring.cloud.config.label=master
#配置中心地址
spring.cloud.config.uri=http://10.130.3.222:6666
#设置为true时,如果服务无法连接到配置中心服务器,则服务启动失败
spring.cloud.config.fail-fast=true
#日志配置,通过配置中心获取
logging.config=${spring.cloud.config.uri}/logback/${spring.application.name}.xml

3.3、文档中心相关的数据库配置

四、效果

4.1、系统说明

  启动我们的文档中心服务后,目前我们已经有如下的服务

  • 注册中心的地址是: http://10.130.3.222:8761/eureka
  • 配置中心的地址是: http://10.130.3.222:6666
  • 网关中心的地址是: http://10.130.3.222:9999
  • 文档中心的地址是: http://10.130.3.66:7777
  • 订单服务的地址是: http://10.130.3.88:7001
  • 库存服务的地址是: http://10.130.3.88:8001

4.2、注册中心效果图

  我们直接请求: http://10.130.3.222:8761/eureka

4.3、nginx配置

  本地的nginx配置:

server{listen 8888;#server_name 127.0.0.1;server_name localhost;charset utf-8;add_header X-Cache $upstream_cache_status;#站点根目录(自定义的,不一定是这个)root html;location / {root   html;index  index.html index.htm;}location ~ ^/gateway/ {rewrite ^/gateway/(.*)$ /$1 break;proxy_redirect off;proxy_set_header Host $host:8888;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_pass http://10.130.3.222:9999;}}

  如果nginx不懂的可以参考我另一篇文章:windows下Nginx配置及负载均衡使用,这里使用到网关中心,需要结合我之前公共api的那个starter一起看。(九)Alian 的 Spring Cloud 公共 API 的(API核心starter),不然你可能会看不懂。

4.4、文档中心效果图

  我们直接请求: http://10.130.3.222:8888/gateway/doc-service/swagger-ui.html


  上面也可以看出服务的地址端口等,后端接口修改了,文档可以很快就刷新。

  至此,我们文档中心已经OK了,前端和后端之间的交互就变简单了,面向文档中心调试就是了,提升了效率,你以为完了么?不,其实后端也会有面临这种问题,比如后端系统间的调用,接口也是非常的多,哪怕是有文档中心,也是很繁琐的,后面我们会自动生成调用api,简化后端之间的交互,想了解就跟我一起来吧!!!

(十一)Alian 的 Spring Cloud 文档中心(swagger聚合文档)相关推荐

  1. (七)Alian 的 Spring Cloud Config 配置中心(客户端)

    目录 一.背景 二.maven依赖 三.配置文件 四.验证 一.背景   通过上一篇文章,我们已经搭建了配置中心了,接下里我们继续改造我们的订单服务了,之前我们的订单服务的数据库配置还是写在配置文件中 ...

  2. (六)Alian 的 Spring Cloud Config 配置中心(服务端)

    目录 一.简介 二.数据库 2.1.应用表 2.2.属性表 2.3.视图 2.4.初始化数据 三.配置 3.1.pom.xml 3.2.application.properties 3.3.主类 3. ...

  3. (八)Alian 的 Spring Cloud Gateway 网关中心

    目录 一.简介 二.配置 2.1.pom文件 三.配置文件 3.1.application.properties 四.主类 一.简介   在微服务架构中,我们的服务往往由多个微服务组成,而这些服务可能 ...

  4. (十八)Alian 的 Spring Cloud Gateway 集群配置

    目录 一.简介 二.配置 三.配置文件 3.1.application.properties 四.主类 五.部署及配置 5.1.部署 5.2.Nginx配置 5.3.Spring Cloud Gate ...

  5. (三)Alian 的 Spring Cloud Eureka Server(服务注册中心)

    目录 一.创建Eureka服务工程 1.1.第一步(创建工程) 1.2.第二步(springboot工程) 1.3.第三步(工程设置) 1.4.第四步(依赖选择) 二.配置 2.1.pom文件 2.2 ...

  6. (十)Alian 的 Spring Cloud 库存系统

    目录 一.背景 二.数据库 1.1.创建数据库 1.2.创建表 1.3.初始数据 1.4.配置中心数据 三.domain-stock 3.1.实体类 3.2.持久层 3.3.打包到私服 四.maven ...

  7. (十五)Alian 的 Spring Cloud 自动生成项目

    目录 一.创建 1.1.第一步 二.mvn命令 三.生成项目 3.1.配置 3.1.属性配置 3.2.控制层 3.3.服务层 四.验证 4.1.请求生成项目 4.2.生成项目结果 4.3.项目结构 一 ...

  8. 玩转Spring Cloud之配置中心(config server config client)

    玩转Spring Cloud之配置中心(config server &config client)  本文内容导航: 一.搭建配置服务中心(config server) 1.1.git方式 1 ...

  9. (十九)Alian 的 Spring Cloud Config 集群配置

    目录 一.简介 1.1.第一步 二.maven依赖 三.配置 3.1.application.properties 3.2.主类 四.客户端修改(支付系统) 4.1 maven依赖 4.2 支付系统主 ...

最新文章

  1. 网络编程学习笔记(TCP回射服务器程序修订版)
  2. 用RadASM 开发窗口程序
  3. E - Another Postman Problem FZU - 2038
  4. dqn系列梳理_DQN是学习归纳出了策略,还是仅仅是memorize了大量的episodes?
  5. 移动开发在路上-- IOS移动开发系列 多线程一 [转]
  6. python编辑器中文字体倒立的_如何用Python+人工识别处理知乎的倒立汉字验证码...
  7. Windows10/Servers2016应用商店恢复/安装
  8. stm32如何执行软复位_stm32f7软件复位 stm32f0 软件复位
  9. 《码出高效:Java开发手册》百度网盘下载
  10. 雅虎宣布其史上最严重数据泄露:5亿账户于2014年被盗
  11. mysql中文本类型的长度限制_关于数据库:MySQL类型文本的最大长度
  12. php上传504,nginx+php设置大文件请求上传(502及504问题处理)
  13. 吃冬瓜对宝宝有什么好处?
  14. 自我总结:找工作面试时注意事项
  15. pli测试50题题库_【马士基销售代表面试】性格测试+12分钟50道题。-看准网
  16. 单片机学习——存储器详解(程序存储器、片内RAM、拓展RAM、EEPROM)
  17. 网站建设服务器拼租服务器好还是独立服务器好
  18. 在Visual Studio代码内部调试节点应用
  19. JavaScript异形滚动轮播
  20. 联想x3650服务器安装硬盘,IBM x3650 M2服务器系统安装攻略(组图)

热门文章

  1. nexus3忘记admin密码!!!
  2. 2020大学计算机学科评估,2020年第五次学科评估大预测:华科将会交出一份怎样的答卷...
  3. android之三大菜单(子菜单,选项菜单,上下文菜单)
  4. 北京四季度平均月薪1.3万,算法类岗位最吸金
  5. 百度paddle ocr 的集成
  6. 学校教育应如何体现真善美这一育人思想?
  7. 飞桨论文复现营 CFDGCN-Paddle
  8. 打印杨辉三角 (20 分)
  9. 母猫守护惨死幼猫尸体一个上午不愿离去(图)
  10. NLP学习—18.Annoy、HNSW、KD tree以及多轮对话