spring cloud微服务项目基于swagger实现的离线api文档:pdf+html

前言:接到任务需要在春天云项目中生成离线api,刚开始也是不清楚如何下手,于是查阅的相关的博客,但是并没有找到在春天云项目中完成离线api相关的博客,经过差不多三天的时间,慢慢的完成了这样一个功能,也是踩了坑,走过弯路,在这里记录一下,分享给大家。

前提:项目是基于spring boot搭建的spring cloud微服务项目,使用swagger对实体类,接口进行相应的注解。在父工程下分为了:服务注册+网关服务+其他功能服务。

介绍:此功能是在网关服务中实现,以下均为Zuul服务器中代码。

1.多服务在线swagger api中首先我们需要实现接口SwaggerResourcesProvider以完成api的切换,离线api需要通过这个接口获取相应的SwaggerResource中的位置作为获取对应服务的api json数据。

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

@Component
@Primary
public class DocumentationConfig implements SwaggerResourcesProvider {
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        resources.add(swaggerResource("服务1", "/serverName1/v2/api-docs", "1.0"));
        resources.add(swaggerResource("服务2", "/serverName2/v2/api-docs", "1.0"));
        resources.add(swaggerResource("服务3", "/serverName3/v2/api-docs", "1.0"));
        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location, String version) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }
}

2.接下来我们建一个测试类,当然你也可以不建测试类,只要在程序中能够获取到其他服务的api json数据都行。生成json数据和通过索引.adoc文件生成overview.adoc,paths .adoc,definitions.adoc这一块网上很多。因为POM文件中只能给出一个生成PDF和HTML文件的目录,所以每生成完一个服务的API文档就需要将它转移到其他目录中,并且删除原目录下文件以备下一次文档的生成,目标目录如:destPdfDirs,destHtmlDirs数组。中间通过调用代码方式执行mvn test指令,过程中会通过overview.adoc,paths.adoc,definitions.adoc三个文件生成pdf和HTML文件。

import com.wode.config.DocumentationConfig;
import com.wode.swagger.MavenUtil;
import io.github.robwin.markup.builder.MarkupLanguage;
import io.github.robwin.swagger2markup.GroupBy;
import io.github.robwin.swagger2markup.Swagger2MarkupConverter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.ResourceUtils;
import springfox.documentation.staticdocs.SwaggerResultHandler;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.io.File;
import java.util.List;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@AutoConfigureRestDocs
@RunWith(SpringRunner.class)
@SpringBootTest
public class SwaggerTest {

    private static Logger logger = LoggerFactory.getLogger(SwaggerTest.class);

    private String snippetDir = "target/generated-snippets";
    private String outputDir  = "target/asciidoc";
    private String pdfFileDir = "target/asciidoc/pdf/index.pdf";
    private String htmlFileDir = "target/asciidoc/html/index.html";
    private String[] destPdfDirs = {"server1/pdf", "server2/pdf", "server3/pdf"};
    private String[] destHtmlDirs = {"server1/html", "server2/html", "server3/html"};
    private String baseDir = "src/main/resources/api/";
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test() throws Exception {
        SwaggerResourcesProvider provider = new DocumentationConfig();
        List<SwaggerResource> resources = provider.get();
        for (int i = 0; i < resources.size(); i++) {
            SwaggerResource resource = resources.get(i);
            // 得到swagger.json,写入outputDir目录中
            mockMvc.perform(get(resource.getLocation()).accept(MediaType.APPLICATION_JSON))
                    .andDo(SwaggerResultHandler.outputDirectory(outputDir).build())
                    .andExpect(status().isOk())
                    .andReturn();
            Swagger2MarkupConverter.from(outputDir + "/swagger.json")
                    .withPathsGroupedBy(GroupBy.TAGS)// 按tag排序
                    .withMarkupLanguage(MarkupLanguage.ASCIIDOC)//格式
                    .withExamples(snippetDir)
                    .build()
                    .intoFolder(outputDir);//输出
            MavenUtil mavenUtil = new MavenUtil();
            mavenUtil.createPdfAndHtmlFile();//执行mvn指令
            long start = System.currentTimeMillis();
            int restrict = 60000;//时间限制1分钟
            while (true) {
                long end = System.currentTimeMillis();
                if (restrict <= (end - start)) {
                    logger.warn("create pdf or html time out. error msg: {}", "path: [" + destPdfDirs[i] + "] or [" + destHtmlDirs[i] + "];");
                    return;
                }
                String pdfPath = mavenUtil.returnPath(pdfFileDir);
                String htmlPath = mavenUtil.returnPath(htmlFileDir);
                File pdfFile = new File(pdfPath);
                File htmlFile = new File(htmlPath);
                if (pdfFile != null && pdfFile.exists() && htmlFile != null && htmlFile.exists()) {
                    File staticPdfFile = ResourceUtils.getFile(mavenUtil.returnPath(baseDir + destPdfDirs[i]) + "/index.pdf");
                    File staticHtmlFile = ResourceUtils.getFile(mavenUtil.returnPath(baseDir + destHtmlDirs[i]) + "/index.html");
                    if (staticPdfFile != null && staticHtmlFile.exists()) {
                        staticPdfFile.delete();
                    }
                    if (staticHtmlFile != null && staticHtmlFile.exists()) {
                        staticHtmlFile.delete();
                    }
                    String destPdfPath = mavenUtil.returnPath(baseDir + destPdfDirs[i]);
                    String destHtmlPath = mavenUtil.returnPath(baseDir + destHtmlDirs[i]);
                    mavenUtil.moveFile(pdfPath, destPdfPath);
                    mavenUtil.moveFile(htmlPath, destHtmlPath);
                    //清空target/asciidoc文件下的文件为下一次生产api准备
                    File file = new File("target/asciidoc");
                    mavenUtil.deleteFile(file);
                    break;
                }
            }
        }
        logger.info("create pdf and html of static api success.");
    }
}

3.pom.xml: “$ {}” 是index.adoc文档生成配置的目录,生成PDF格式的目录,生成HTML的目录注意导入依赖的版本,若因为版本不一致可能导致一些NoSuchMethod的异常, swagger的依赖要使用2.6.1版本,否则也会有冲突。

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
        <asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
        <generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
        <asciidoctor.html.output.directory>${project.build.directory}/asciidoc</asciidoctor.html.output.directory>
        <asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc</asciidoctor.pdf.output.directory>
    </properties>
<!-- 以下为增加的依赖 -->
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <!--离线文档-->
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <version>1.1.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!--springfox-staticdocs 生成静态文档-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-staticdocs</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>io.github.robwin</groupId>
            <artifactId>swagger2markup</artifactId>
            <version>0.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.10</version>
        </dependency>
<!-- 下面的内容网上很多很多很多 -->
<build>
        <plugins>
            <!-- 通过mvn指令执行单元测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.asciidoctor</groupId>
                <artifactId>asciidoctor-maven-plugin</artifactId>
                <version>1.5.3</version>
                <!--生成PDF-->
                <dependencies>
                    <dependency>
                        <groupId>org.asciidoctor</groupId>
                        <artifactId>asciidoctorj-pdf</artifactId>
                        <version>1.5.0-alpha.14</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jruby</groupId>
                        <artifactId>jruby-complete</artifactId>
                        <version>1.7.21</version>
                    </dependency>
                </dependencies>
                <!--文档生成配置-->
                <configuration>
                    <sourceDirectory>${asciidoctor.input.directory}</sourceDirectory>
                    <sourceDocumentName>index.adoc</sourceDocumentName>
                    <attributes>
                        <doctype>book</doctype>
                        <toc>left</toc>
                        <toclevels>3</toclevels>
                        <numbered></numbered>
                        <hardbreaks></hardbreaks>
                        <sectlinks></sectlinks>
                        <sectanchors></sectanchors>
                        <generated>${generated.asciidoc.directory}</generated>
                    </attributes>
                </configuration>
                <executions>
                    <!--html5-->
                    <execution>
                        <id>output-html</id>
                        <phase>test</phase>
                        <goals>
                            <goal>process-asciidoc</goal>
                        </goals>
                        <configuration>
                            <backend>html5</backend>
                            <outputDirectory>${asciidoctor.html.output.directory}/html</outputDirectory>
                        </configuration>
                    </execution>
                    <!--pdf-->
                    <execution>
                        <id>output-pdf</id>
                        <phase>test</phase>
                        <goals>
                            <goal>process-asciidoc</goal>
                        </goals>
                        <configuration>
                            <backend>pdf</backend>
                            <outputDirectory>${asciidoctor.pdf.output.directory}/pdf</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

4.index.adoc:这个是文档生成配置文件,POM配置中要能找到这个文件就行。

include::{generated}/overview.adoc[]
include::{generated}/definitions.adoc[]
include::{generated}/paths.adoc[]

5.maven指令文件:这两个文件是调用代码是生成的指令文件。

6.MavenUtil:执行maven指令和一些文件的操作。注意执行的maven指令必须是在当前项目下也就是在网关服务中执行mvn test。

import java.io.*;
import java.nio.file.Paths;

public class MavenUtil {

    public void createPdfAndHtmlFile() throws Exception {
        //获取pom.xml的绝对路径
        String pomPath = returnPath("pom.xml");

        //生成bat文件
        StringBuilder batFileContent = new StringBuilder(pomPath.substring(0, 2)).append("\r\n");
        batFileContent.append("cd ").append(pomPath.substring(0, pomPath.length() - 7)).append("\r\n");
        batFileContent.append("mvn test");

        //将batFileContent写入bat文件中
        writeInBatFile("src/cmd/bat.bat", batFileContent.toString().replace("/", "\\"));

        //生成vbs文件内容
        StringBuilder vbsFileContent = new StringBuilder("Set ws = CreateObject(\"Wscript.Shell\")").append("\r\n");
        vbsFileContent.append("ws.run \"cmd /c ").append(returnPath("src/cmd/bat.bat").replace("/", "\\")).append("\",vbhide");

        //写入vbs文件
        writeInBatFile("src/cmd/vbs.vbs", vbsFileContent.toString());

        //生成执行vbs文件命令(用vbs隐藏bat文件执行时的窗口)
        StringBuilder cmd = new StringBuilder("cmd /c CScript ").append(returnPath("src/cmd/vbs.vbs"));
        System.out.println(cmd.toString());
        Process process = Runtime.getRuntime().exec(cmd.toString());
        System.out.println(process.waitFor());
    }

    public String returnPath(String filePath) {
        return Paths.get(filePath).toAbsolutePath().toString();
    }

    private void writeInBatFile(String fileName, String content) {
        File file = new File(returnPath(fileName));
        //如果文件存在则删除
        if (file != null && file.exists()) {
            file.delete();
        }
        try {
            //创建GBK格式的bat文件,utf-8中文在cmd中乱码
            OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), "GBK");
            BufferedWriter bw = new BufferedWriter(out);
            bw.write(content);
            bw.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean moveFile(String fileName, String destinationFloderUrl) {
        File file = new File(fileName);
        File destFloder = new File(destinationFloderUrl);
        if (destFloder.exists()) {
            if (destFloder.isFile()) {
                return false;
            }
        } else {
            if (!destFloder.mkdirs()) {
                return false;
            }
        }
        if (file.isFile() && file.exists()) {
            String destinationFile = destinationFloderUrl + "/" + file.getName();
            if (!file.renameTo(new File(destinationFile))) {
                return false;
            }
        } else {
            return false;
        }
        return true;
    }

    public void deleteFile(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                if (f.isFile()) {
                    f.delete();
                } else if (file.isDirectory()) {
                    deleteFile(f);
                }
            }
            file.delete();
        }
    }
}

7.代码完了,要生成所有服务的离线API文档,需要先将注册服务跑起来,再将需要生产离线API的服务跑起来,最后跑网关服务中的测试类就行了。下面是其中一个HTML,暂时还没有去解决PDF中文缺失的问题,大家可以参考一下其他伙伴们的经验

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值