(十一)Alian 的 Spring Cloud 文档中心(swagger聚合文档)
目录
- 一、简介
- 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聚合文档)相关推荐
- (七)Alian 的 Spring Cloud Config 配置中心(客户端)
目录 一.背景 二.maven依赖 三.配置文件 四.验证 一.背景 通过上一篇文章,我们已经搭建了配置中心了,接下里我们继续改造我们的订单服务了,之前我们的订单服务的数据库配置还是写在配置文件中 ...
- (六)Alian 的 Spring Cloud Config 配置中心(服务端)
目录 一.简介 二.数据库 2.1.应用表 2.2.属性表 2.3.视图 2.4.初始化数据 三.配置 3.1.pom.xml 3.2.application.properties 3.3.主类 3. ...
- (八)Alian 的 Spring Cloud Gateway 网关中心
目录 一.简介 二.配置 2.1.pom文件 三.配置文件 3.1.application.properties 四.主类 一.简介 在微服务架构中,我们的服务往往由多个微服务组成,而这些服务可能 ...
- (十八)Alian 的 Spring Cloud Gateway 集群配置
目录 一.简介 二.配置 三.配置文件 3.1.application.properties 四.主类 五.部署及配置 5.1.部署 5.2.Nginx配置 5.3.Spring Cloud Gate ...
- (三)Alian 的 Spring Cloud Eureka Server(服务注册中心)
目录 一.创建Eureka服务工程 1.1.第一步(创建工程) 1.2.第二步(springboot工程) 1.3.第三步(工程设置) 1.4.第四步(依赖选择) 二.配置 2.1.pom文件 2.2 ...
- (十)Alian 的 Spring Cloud 库存系统
目录 一.背景 二.数据库 1.1.创建数据库 1.2.创建表 1.3.初始数据 1.4.配置中心数据 三.domain-stock 3.1.实体类 3.2.持久层 3.3.打包到私服 四.maven ...
- (十五)Alian 的 Spring Cloud 自动生成项目
目录 一.创建 1.1.第一步 二.mvn命令 三.生成项目 3.1.配置 3.1.属性配置 3.2.控制层 3.3.服务层 四.验证 4.1.请求生成项目 4.2.生成项目结果 4.3.项目结构 一 ...
- 玩转Spring Cloud之配置中心(config server config client)
玩转Spring Cloud之配置中心(config server &config client) 本文内容导航: 一.搭建配置服务中心(config server) 1.1.git方式 1 ...
- (十九)Alian 的 Spring Cloud Config 集群配置
目录 一.简介 1.1.第一步 二.maven依赖 三.配置 3.1.application.properties 3.2.主类 四.客户端修改(支付系统) 4.1 maven依赖 4.2 支付系统主 ...
最新文章
- 网络编程学习笔记(TCP回射服务器程序修订版)
- 用RadASM 开发窗口程序
- E - Another Postman Problem FZU - 2038
- dqn系列梳理_DQN是学习归纳出了策略,还是仅仅是memorize了大量的episodes?
- 移动开发在路上-- IOS移动开发系列 多线程一 [转]
- python编辑器中文字体倒立的_如何用Python+人工识别处理知乎的倒立汉字验证码...
- Windows10/Servers2016应用商店恢复/安装
- stm32如何执行软复位_stm32f7软件复位 stm32f0 软件复位
- 《码出高效:Java开发手册》百度网盘下载
- 雅虎宣布其史上最严重数据泄露:5亿账户于2014年被盗
- mysql中文本类型的长度限制_关于数据库:MySQL类型文本的最大长度
- php上传504,nginx+php设置大文件请求上传(502及504问题处理)
- 吃冬瓜对宝宝有什么好处?
- 自我总结:找工作面试时注意事项
- pli测试50题题库_【马士基销售代表面试】性格测试+12分钟50道题。-看准网
- 单片机学习——存储器详解(程序存储器、片内RAM、拓展RAM、EEPROM)
- 网站建设服务器拼租服务器好还是独立服务器好
- 在Visual Studio代码内部调试节点应用
- JavaScript异形滚动轮播
- 联想x3650服务器安装硬盘,IBM x3650 M2服务器系统安装攻略(组图)
热门文章
- nexus3忘记admin密码!!!
- 2020大学计算机学科评估,2020年第五次学科评估大预测:华科将会交出一份怎样的答卷...
- android之三大菜单(子菜单,选项菜单,上下文菜单)
- 北京四季度平均月薪1.3万,算法类岗位最吸金
- 百度paddle ocr 的集成
- 学校教育应如何体现真善美这一育人思想?
- 飞桨论文复现营 CFDGCN-Paddle
- 打印杨辉三角 (20 分)
- 母猫守护惨死幼猫尸体一个上午不愿离去(图)
- NLP学习—18.Annoy、HNSW、KD tree以及多轮对话