借助 maven plugin + 自定义服务Swagger2Word服务,实现满足自定义格式需求的RESTful API文档的实时更新和在线展示。(注意:这里的API接口文档是独立于应用的,不似Swagger那样必须启动应用才能看到相应文档。)
通过文档自动化地实时在线更新, 杜绝低级人工错误,实现引入错误的快速发现,降低双方的沟通摩擦成本,加速开发效率。
现阶段我们会一次性生成word,html两种文件类型的接口文档。其中word格式的提供给公司外部的系统对接方,html格式的则提供内部的实时在线查看。文档内容格式如下(尚未经过设计,凑活看):
我们需要使用到三个Maven Plugin,以及一个自定义的服务。
在这一步我们需要借助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>
借助我们自定义的RESTFul服务,将上一步生成的JSON文件转换为自定义内容格式的Word / HTML文件(其中HTML格式就是用作在线更新的)。这一步将使用到Maven Plugin之exec-maven-plugin
。
这一步原本笔者初步的想法是实现一个自定义Maven Plugin,但在开始时突然福至心灵地想到为啥不借鉴微服务的思路,这样的优势:
闲话扯远了,咱们拉回来,以下是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;
}
最后就只剩下将生成的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>
以上配置完成之后,接下来要做的就是在命令行依次敲出如下指令并回车:
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
秉承送佛送到西,以下也给出笔者在使用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;
}
其实上面的2.4小节里已经给出了思路,具体嵌入到哪里就看读者所在公司的自动化进度以及流程特点了。
通过全自动的文档生成,促进研发过程中定期审查机制的执行,检查人无需启动相关应用,即可检查应用的基本情况 —— 注解说明是否完备,设计是否合理/被违背等。(这其实还是自动化程度不够导致系统可能启动非常麻烦),定期的检查远远好过毕其功于一役的最终的一次性测试和检查,把功夫做在平时。