• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
夫礼者
帮助1

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

1. 前言

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

2. 效果图

现阶段我们会一次性生成word,html两种文件类型的接口文档。其中word格式的提供给公司外部的系统对接方,html格式的则提供内部的实时在线查看。文档内容格式如下(尚未经过设计,凑活看):
学新通

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 依赖是设置了默认版本号的),其它细节参见下方源码。

<!--	Swagger注解导出为Word文档 开始		-->
<plugin>
    <groupId>com.github.kongchen</groupId>
    <artifactId>swagger-maven-plugin</artifactId>
    <version>3.1.8</version>
    <configuration>
        <apiSources>
            <apiSource>
               <!-- Tell the plugin your project is a JAX-RS(false) or a SpringMvc(true) project-->
                <springmvc>true</springmvc>
                <locations>
                    <!-- required-->
                    <!--   Classes containing Swagger's annotation @Api,
                    or packages containing those classes can be configured here.
                     Each item must be located inside a tag-->
                    <location>org.xx.xx.xx.controller</location>
                </locations>
                <!-- 这一步为扩展支持knife4j, 具体作用参见下面的小节 -->
                <swaggerExtensions>
                     <swaggerExtension>com.github.kongchen.swagger.docgen.extend.Knife4jApiOperationSupportVendorExtension</swaggerExtension>
                </swaggerExtensions>
                <schemes>
                    <scheme>http</scheme>
                    <scheme>https</scheme>
                </schemes>
                <host></host>
                <basePath></basePath>
                <!-- <typesToSkip></typesToSkip>  -->
                <info>
                    <!--required-->
                    <title>title</title>
                    <version>v1</version>
                    <description>description</description>
                    <termsOfService>
                        http://www.github.com/kongchen/swagger-maven-plugin
                    </termsOfService>
                    <contact>
                        <email>x</email>
                        <name>x</name>
                        <url>x</url>
                    </contact>
                    <license>
                        <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
                        <name>Apache 2.0</name>
                    </license>
                </info>
                <outputFormats>json</outputFormats>
                <!--以项目根目录为基础 -->
                <swaggerDirectory>generated/swagger-ui</swaggerDirectory>
               	<!-- 这里我们使用自定义模板 -->
                <!--  Support classpath or file absolute path here. 1) classpath e.g: "classpath:/markdown.hbs", "classpath:/templates/hello.html" 2) file e.g: "${basedir}/src/main/resources/markdown.hbs", "${basedir}/src/main/resources/template/hello.html" 
                            <templatePath>${basedir}/templates/strapdown.html.hbs</templatePath>
                            <outputPath>${basedir}/generated/document.html</outputPath> -->              
            </apiSource>
        </apiSources>
    </configuration>
    <executions>
        <execution>
            <phase>compile</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>
学新通
3.1.1 实现扩展 - 支持knife4j

以下以knife4j中的注解ApiOperationSupport为例,介绍如何借助swagger-maven-plugin提供的扩展机制,支持API排序功能。

正式开始前,让我们介绍下,截至目前,最新的swagger-maven-plugin版本号为3.1.9-SNAPSHOT, 其提供的扩展机制主要为如下三种:

  1. 自定义实现ClassSwaggerReader
  2. 自定义SwaggerExtension实现类。
  3. 自定义SwaggerSpecFilter实现类。 (不过在这个实现类里,你无法拿到注解所修饰的那个原始Method信息)

相关的配置如下:

<apiSource>
	<!--1. 实现com.github.kongchen.swagger.docgen.reader.ClassSwaggerReader 接口-->
	<!-- 范例参见: 官方单元测试代码中的 VendorExtensionsSpringMvcReader 类 -->
	<swaggerApiReader>com.wordnik.swagger.jaxrs.reader.JaxrsReader</swaggerApiReader>
    <!--2. 实现 io.swagger.jaxrs.ext.SwaggerExtension 接口 ; 参见文档 https://gitee.com/lqzkcx3/swagger-maven-plugin#a-sample-configuration -->
    <!-- 范例参见: 官方单元测试代码中的 TestVendorExtension 类 -->
	<swaggerExtensions>
        <swaggerExtension>com.example.VendorExtension</swaggerExtension>
    </swaggerExtensions>    
    <!-- 3. 实现 io.swagger.core.filter.SwaggerSpecFilter接口 ; 这一个在官方给出的源码种并没有显式说明, 但确实可用 -->
    <!-- 源码中的应用位置: AbstractDocumentSource.doFilter(Swagger swagger) -->
    <swaggerInternalFilter>xxxx</swaggerInternalFilter>
</apiSource>

针对注解的支持,我们只需要提供如下使用类:

public class Knife4jApiOperationSupportVendorExtension extends AbstractSwaggerExtension {
    @Override
    public void decorateOperation(final Operation operation, final Method method, final Iterator<SwaggerExtension> chain) {

        final ApiOperationSupport annotation = method.getAnnotation(ApiOperationSupport.class);
        if (annotation != null) {
            operation.setVendorExtension("x-order", annotation.order());
            operation.setVendorExtension("x-author", annotation.author());
        }        

        super.decorateOperation(operation, method, chain);
    }
}

然后将以上扩展类进行注册即可,相关配置形式上面已经给出。

最终效果是在生成的swagger.json文件中,新增了x-order,x-author两个扩展属性值,这样我们就可以在之后的word生成过程中,针对这两个扩展出来的字段进行自定义处理,比如排序。
学新通

补充几点:

  1. 如果需要隐藏API,使用注解 @ApiOperation(value = "ignore", hidden=true) , 注意 @ApiIgnore 是无效的。
  2. 如果Controller方法注解使用:@RequestMapping 必须标识method 例如 @RequestMapping(value = "/getCount", method = RequestMethod.POST),如果不标识,就不会生成该接口文档。也可以使用@PostMapping注解。
  3. 关于@ApiIgnore,建议在 SpringMvcApiReader.parseMethodSpringMvcApiReader.read(Set<Class<?>> classes) 中扩展。

3.2 生成Word文档

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

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

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

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

<!--	Swagger注解导出为Word文档 		-->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>generateSwaggerWordFile</id>
            <phase>pre-package</phase>
            <goals>
                <goal>java</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <mainClass>
            org.xx.xx.MavenExecPluginGenerateSwaggerWordFile
        </mainClass>
        <cleanupDaemonThreads>false</cleanupDaemonThreads>
        <!-- -->
        <addResourcesToClasspath>
            true
        </addResourcesToClasspath>
        <addOutputToClasspath>true</addOutputToClasspath>

        <arguments>
            <!-- // 程序的命令行参数 // -->
            <!-- 构建目录,缺省为target -->
            <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>
            <!-- swagger文档生成相关 -->
            <argument>generated/swagger-ui/swagger.json</argument>
            <argument>http://xxx.xx.x.x:9527/strToWord</argument>
            <!-- 这是classpath属性,其值就是下面的<classpath/> -->
            <argument>-classpath</argument>
        </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

相关源码如下:

<!--上传到在线文件服务器-->
<!-- https://www.cnblogs.com/tqyysm/articles/9815092.html -->
<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>wagon-maven-plugin</artifactId>
	<dependencies>
		<dependency>
			<groupId>com.jcraft</groupId>
			<artifactId>jsch</artifactId>
			<version>0.1.54</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk16</artifactId>
			<version>1.46</version>
		</dependency>
	</dependencies>
	<configuration>
		<serverId>txYunServer</serverId>
		<!-- 需要部署的文件 -->
		<fromFile>generated/swagger-ui/xxx-yyy.html</fromFile>
		<!-- 部署目录  用户:密码@ip 部署地址:端口 root:password@ -->
		<url>scp://xxx.yy.zzz.24/usr/local/docker-nginx/html</url>					
		<commands>						
			 <!-- <command>pkill java</command>  -->  
			 <!-- 以天为单位对文档进行更新 -->
			<command><![CDATA[mv /usr/local/docker-nginx/html/kanq-authcenter.html /usr/local/docker-nginx/html/kanq-authcenter-`date  %Y-%m-%d`.html]]></command>
		</commands>
		<!-- 显示运行命令的输出结果  -->
		<displayCommandOutputs>true</displayCommandOutputs>
	</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文档的。
  2. Knife4jApiOperationSupportVendorExtension 。没有必要引入笔者这个JAR,只需要引入这个类即可。
  3. swagger-maven-plugin - 夫礼者
  4. swagger-maven-plugin另外一个扩展范例
  5. 扩展 swagger支持文档自动列举所有枚举值

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfkbgec
系列文章
更多 icon
同类精品
更多 icon
继续加载