【DEVOPS】实现Swagger2应用接口文档Word格式的在线实时更新

借助 maven plugin + 自定义服务Swagger2Word服务,实现满足自定义格式需求的RESTful API文档的实时更新和在线展示。(注意:这里的API接口文档是独立于应用的,不似Swagger那样必须启动应用才能看到相应文档。)

0. 目录

      • 1. 前言
      • 2. 效果图
      • 3. 实现
        • 3.1 Swagger2.0 格式规范文件的生成
        • 3.2 生成Word文档
        • 3.3 上传文档
        • 3.4 执行完整操作
      • 4. 补充 - 搭建静态文件服务器
      • 5. 补充 - 集成到DEVOPS流程
      • 6. 罗嗦几句
      • 7. 参考

1. 前言

通过文档自动化地实时在线更新, 杜绝低级人工错误,实现引入错误的快速发现,降低双方的沟通摩擦成本,加速开发效率。

2. 效果图

现阶段我们会一次性生成word,html两种文件类型的接口文档。其中word格式的提供给公司外部的系统对接方,html格式的则提供内部的实时在线查看。文档内容格式如下(尚未经过设计,凑活看):
【DEVOPS】实现Swagger2应用接口文档Word格式的在线实时更新_第1张图片

3. 实现

我们需要使用到三个Maven Plugin,以及一个自定义的服务。

3.1 Swagger2.0 格式规范文件的生成

在这一步我们需要借助Maven Plugin之 swagger-maven-plugin,实现在Maven的 Compile阶段,基于项目源码中的Swagger注解生成符合Swagger2.0格式规范的JSON文件。( Swagger2.0格式规范具体要求参见 Swagger2.0文档格式规范 )

这一步最大的难点是针对不同版本的swagger-maven-plugin,其配置上略有差异,因此这里笔者直接给出明确的版本号(其实SpringBoot的 parent 依赖是设置了默认版本号的),其它细节参见下方源码。


<plugin>
    <groupId>com.github.kongchengroupId>
    <artifactId>swagger-maven-pluginartifactId>
    <version>3.1.8version>
    <configuration>
        <apiSources>
            <apiSource>
               
                <springmvc>truespringmvc>
                <locations>
                    
                    
                    <location>org.xx.xx.xx.controllerlocation>
                locations>
                <schemes>
                    <scheme>httpscheme>
                    <scheme>httpsscheme>
                schemes>
                <host>host>
                <basePath>basePath>
                
                <info>
                    
                    <title>titletitle>
                    <version>v1version>
                    <description>descriptiondescription>
                    <termsOfService>
                        http://www.github.com/kongchen/swagger-maven-plugin
                    termsOfService>
                    <contact>
                        <email>xemail>
                        <name>xname>
                        <url>xurl>
                    contact>
                    <license>
                        <url>http://www.apache.org/licenses/LICENSE-2.0.htmlurl>
                        <name>Apache 2.0name>
                    license>
                info>
                <outputFormats>jsonoutputFormats>
                
                <swaggerDirectory>generated/swagger-uiswaggerDirectory>
               	
                              
            apiSource>
        apiSources>
    configuration>
    <executions>
        <execution>
            <phase>compilephase>
            <goals>
                <goal>generategoal>
            goals>
        execution>
    executions>
plugin>

3.2 生成Word文档

借助我们自定义的RESTFul服务,将上一步生成的JSON文件转换为自定义内容格式的Word / HTML文件(其中HTML格式就是用作在线更新的)。这一步将使用到Maven Plugin之exec-maven-plugin

这一步原本笔者初步的想法是实现一个自定义Maven Plugin,但在开始时突然福至心灵地想到为啥不借鉴微服务的思路,这样的优势:

  1. 可以实现服务功能的单一,使得维护更新方便。过往我们一直试图打造一个统一平台,将内部需要的功能整合进一个应用里,这到底服务更新和代码同步经常出现问题,而且因为需求各异,各类依赖集成在一起之后很容易出现冲突,使得开发环境下应用的启动调试都心惊胆颤,这会打击开发人员的积极性。
  2. 提升相关人员的Owner感觉,确保了需求的响应速度和质量。过往的大应用服务形式,相关子服务的编写者很容易表现出听之任之的态度。而微服务化拆分服务之后,自主权的提升能够显著提高人员的积极性。(除了接口规范,我们不会限制语言和第三方依赖种类)
  3. 积累微服务相关的实践经验。作为基础设施极度缺乏,独立部署需求强烈的业务型软件公司,贸然追逐微服务的时髦只会导致各方的苦不堪言,而通过实现内部服务的微服务化,我们可以在实践中去感受和积累微服务的相关经验,探索出适合自己公司业务特点的微服务实战技术路线。

闲话扯远了,咱们拉回来,以下是exec-maven-plugin的相关配置:


<plugin>
    <groupId>org.codehaus.mojogroupId>
    <artifactId>exec-maven-pluginartifactId>
    <version>3.0.0version>
    <executions>
        <execution>
            <id>generateSwaggerWordFileid>
            <phase>pre-packagephase>
            <goals>
                <goal>javagoal>
            goals>
        execution>
    executions>
    <configuration>
        <mainClass>
            org.xx.xx.MavenExecPluginGenerateSwaggerWordFile
        mainClass>
        <cleanupDaemonThreads>falsecleanupDaemonThreads>
        
        <addResourcesToClasspath>
            true
        addResourcesToClasspath>
        <addOutputToClasspath>trueaddOutputToClasspath>

        <arguments>
            
            
            <argument>${project.build.directory}argument>
            <argument>${session.executionRootDirectory}argument>
            
            <argument>${project.groupId}argument>
            <argument>${project.artifactId}argument>
            <argument>${project.version}argument>
            <argument>${project.packaging}argument>
            
            <argument>generated/swagger-ui/swagger.jsonargument>
            <argument>http://xxx.xx.x.x:9527/strToWordargument>
            
            <argument>-classpathargument>
        arguments>
    configuration>
plugin>

相关Java代码:

	// ==== MavenExecPluginGenerateSwaggerWordFile.java核心代码
	public static void main(String[] args) {
     
		if (args.length < 5) {
     
			output("缺少必要参数, 不执行 MavenExecPluginGenerateMetaFile.java");
			return;
		}

		// debugInputParams(args);

		MavenProjectEntity entity = readArgs(args);

		final String swaggerJsonFileRelativePath = args[6];
		final String swagger2WordServerUrl = args[7];

		final String swaggerJsonContent = FileUtil.readString(
				FilenameUtil.concat(entity.getExecutionRootDirectory(), swaggerJsonFileRelativePath),
				CharsetUtil.CHARSET_UTF_8);

		final File wordFileGenerated = FileUtil
				.file(FilenameUtil.concat(entity.getExecutionRootDirectory(), swaggerJsonFileRelativePath + ".doc"));
				// 请求远程RESTful服务,转换JSON文件
		HttpRequest.post(swagger2WordServerUrl).queryMap(Collections.singletonMap("jsonStr", swaggerJsonContent))//
				.connectTimeout(Duration.ofSeconds(6000))// 
				.readTimeout(Duration.ofSeconds(6000))//
				.writeTimeout(Duration.ofSeconds(6000))//
				.execute() //
				.onFailed((request, ex) -> Console.error("发生异常:" + ex))//
				.onSuccess(s -> s.rawBody(t -> FileUtil.writeFromStream(t.byteStream(), wordFileGenerated)));

		// 同时生成一份html文件
		FileUtil.copy(wordFileGenerated, FileUtil.file(FilenameUtil.concat(entity.getExecutionRootDirectory(),
				FilenameUtil.getFullPath(swaggerJsonFileRelativePath), "xxx-yyy.html")), true);
		output("接口word文档生成完成!路径为: " + wordFileGenerated.getAbsolutePath());

	}

	static MavenProjectEntity readArgs(String[] args) {
     
		MavenProjectEntity entity = new MavenProjectEntity();
		entity.setProjectBuildDirectory(args[0]);
		entity.setExecutionRootDirectory(args[1]);
		entity.setGroupId(args[2]);
		entity.setArtifactId(args[3]);
		entity.setVersion(args[4]);
		entity.setPacking(args[5]);
		return entity;
	}

3.3 上传文档

最后就只剩下将生成的HTML格式的接口文档推送到早已部署好的文件服务器指定目录下。这一步我们需要使用到Maven Plugin之wagon-maven-plugin

相关源码如下:



<plugin>
	<groupId>org.codehaus.mojogroupId>
	<artifactId>wagon-maven-pluginartifactId>
	<dependencies>
		<dependency>
			<groupId>com.jcraftgroupId>
			<artifactId>jschartifactId>
			<version>0.1.54version>
		dependency>
		<dependency>
			<groupId>org.bouncycastlegroupId>
			<artifactId>bcprov-jdk16artifactId>
			<version>1.46version>
		dependency>
	dependencies>
	<configuration>
		<serverId>txYunServerserverId>
		
		<fromFile>generated/swagger-ui/xxx-yyy.htmlfromFile>
		
		<url>scp://xxx.yy.zzz.24/usr/local/docker-nginx/htmlurl>					
		<commands>						
			   
			 
			<command>command>
		commands>
		
		<displayCommandOutputs>truedisplayCommandOutputs>
	configuration>
plugin>

3.4 执行完整操作

以上配置完成之后,接下来要做的就是在命令行依次敲出如下指令并回车:

cd {
     项目根目录}
# 生成swagger2.0 JSON文件
mvn swagger:generate
# 生成 word/html自定义内容格式文档
mvn exec:java
# 将html格式的文档推送到静态文件服务器
mvn wagon:upload-single wagon:sshexec

###### 将以上命令组装起来
mvn swagger:generate exec:java wagon:upload-single wagon:sshexec

4. 补充 - 搭建静态文件服务器

秉承送佛送到西,以下也给出笔者在使用docker搭建nginx静态文件服务器的相关命令和配置。

##### 宿主机上需要准备的nginx配置文件
# /usr/local/docker-nginx目录下添加如下文件(内容参见本小节尾部):
nginx.conf   
conf.d/default.conf

docker run --name nginx_container -p 9527:80 -d -e TZ="Asia/Shanghai" -v /etc/localtime:/etc/localtime:ro -v /usr/local/docker-nginx/html:/usr/share/nginx/html -v /usr/local/docker-nginx/nginx.conf:/etc/nginx/nginx.conf:ro -v /usr/local/docker-nginx/conf.d/:/etc/nginx/conf.d/  -v /usr/local/docker-nginx/logs:/var/log/nginx nginx

###################################### default.conf内容
server {
     
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
     
		autoindex on;
		autoindex_exact_size off;    #设置以MB、GB等单位显示文件大小
		autoindex_localtime on;    #设置显示目录或文件的时间属性

        alias /usr/share/nginx/html/;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
     
        root   /usr/share/nginx/html;
    }

}

###################################### nginx.conf内容
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
     
    worker_connections  1024;
}


http {
     
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}



5. 补充 - 集成到DEVOPS流程

其实上面的2.4小节里已经给出了思路,具体嵌入到哪里就看读者所在公司的自动化进度以及流程特点了。

6. 罗嗦几句

通过全自动的文档生成,促进研发过程中定期审查机制的执行,检查人无需启动相关应用,即可检查应用的基本情况 —— 注解说明是否完备,设计是否合理/被违背等。(这其实还是自动化程度不够导致系统可能启动非常麻烦),定期的检查远远好过毕其功于一役的最终的一次性测试和检查,把功夫做在平时。

7. 参考

  1. GitHub - Swagger2Word 我们就是基于这个开源项目实现的Swagger注解转Word文档的。

你可能感兴趣的:(DevOps,devops,swagger2,word)