前后端分离时如何优雅的编写 API 文档

本贴最后更新于 1622 天前,其中的信息可能已经时移世易

在前后端分离的项目中,难免会涉及到接口文档的编写维护问题,正好最近的项目就涉及到了这个方面的东西,为此,有必要记录一下如何优雅的完成这一巨坑的填补。

本次使用的工具是 swagger2,swagger-ui,swagger2markup,knife4j,后边将对这些插件做一个简单的介绍。

1 介绍

swagger 是一个 API 接口文档生成工具,官网:https://swagger.io。项目集成 swagger 后,只需要简单的几个注解便可以在编写接口的同时完成文档的编写,配合 swagger-ui,便可以在项目启动后,直接浏览器访问指定地址,一般是:

http://{IP}:{端口}/{项目名}/swagger-ui.html

调试 API。而整合 swagger2markup 后,还可以将 API 直接导出成 html、markdown 等格式的文档。

knife4j,前身的 swagger-bootstrap-ui,是 Swagger 生成 Api 文档的增强解决方案,具体介绍这里不再细说,直接上官网:https://doc.xiaominfo.com 。集成这个插件,可以换掉 swagger-ui 丑陋的皮肤,还能完成 API 的排序,同时也能直接生成 markdown 格式的文档。话不多说,开搞!

2 新建项目

2.1 创建

这里创建一个 springboot 项目,几个简单的配置项以后,便是等待下载完成。

1.png
2.png
3.png

2.2 controller

新建一个 controller 包,并创建一个 TestController

@RestController
@RequestMapping("/v1")
public class TestController {

    @RequestMapping(value = "/test1", method = RequestMethod.GET)
    public String test1(@RequestParam String username, @RequestParam String password) {
        return username + password;
    }
}

3 集成 swagger

3.1 添加依赖

项目创建完成后,在 pom.xml 中添加如下的依赖:

    <repositories>
        <repository>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
            </snapshots>
            <id>jcenter-releases</id>
            <name>jcenter</name>
            <url>http://jcenter.bintray.com</url>
        </repository>
    </repositories>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
	<!-- 导出html、markdown等 -->
        <dependency>
            <groupId>io.github.swagger2markup</groupId>
            <artifactId>swagger2markup</artifactId>
            <version>1.3.1</version>
        </dependency>

将来导出 html 需要的插件配置

	<plugins>
            <plugin>
                <groupId>org.asciidoctor</groupId>
                <artifactId>asciidoctor-maven-plugin</artifactId>
                <version>1.5.6</version>
                <configuration>
                    <sourceDirectory>src/docs/asciidoc/generated</sourceDirectory>
                    <outputDirectory>src/docs/asciidoc/html</outputDirectory>
                    <headerFooter>true</headerFooter>
                    <doctype>book</doctype>
                    <backend>html</backend>
                    <sourceHighlighter>coderay</sourceHighlighter>
                    <attributes>
                        <!--菜单栏在左边-->
                        <toc>left</toc>
                        <!--多标题排列-->
                        <toclevels>3</toclevels>
                        <!--自动打数字序号-->
                        <sectnums>true</sectnums>
                    </attributes>
                </configuration>
            </plugin>
        </plugins>

3.2 配置 swagger2

新建一个 config 包,并创建一个配置类:SwaggerConfig.java

@Configuration
public class SwaggerConfig {
    // 接口大标题
    private final String title = "测试API";
    // 具体的描述
    private final String description = "测试项目API文档";
    // 接口版本号
    private final String version = "1.0.0";
    // 服务说明url,服务条款
    private final String termsOfServiceUrl = "http://blog.kangaroohy.top";
    // licence,许可证
    private final String license = "MIT";
    // licnce url,许可网址
    private final String licenseUrl = "https://mit-license.org/";
    // 接口作者联系方式
    private final Contact contact = new Contact("kangaroo1122", "http://blog.kangaroohy.top", "326170945@qq.com");

    @Bean
    public Docket buildDocket() {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(buildApiInf())
                .groupName("v1-version-api")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.kangaroohy.swagger.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo buildApiInf() {
        return new ApiInfoBuilder().title(title).termsOfServiceUrl(termsOfServiceUrl).description(description)
                .version(version).license(license).licenseUrl(licenseUrl).contact(contact).build();
    }
}

同时在项目启动类添加一个注解:@EnableSwagger2

@SpringBootApplication
@EnableSwagger2
public class SwaggerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SwaggerApplication.class, args);
    }

}

接着,需要在 controller 中加上 swagger 的注解:

@RestController
@RequestMapping("/v1")
@Api(tags = {"Test接口测试"})
public class TestController {

    @RequestMapping(value = "/test1", method = RequestMethod.GET)
    @ApiOperation(value = "测试url-test1", httpMethod = "GET", produces = "application/json", notes = "用于获取测试数据")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username", value = "姓名", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "password", value = "密码", required = true, dataType = "String", paramType = "query")
    })
    public String test1(@RequestParam String username, @RequestParam String password) {
        return username + password;
    }
}

各个注解在这里做一个简单的介绍。

3.2.1 @Api 注解

说明每个 controller 层控制类的作用,即每个请求类
属性:tags:描述该类的作用

3.2.2 @ApiOperation 注解

作用在控制类中的每个方法上,说明该类的作用
属性:value:说明该类的作用

3.2.3 @ApiParam 注解

@ApiParam 作用于请求方法上,定义 api 参数的注解,属性有:
name:api 参数的英文名
value:api 参数的描述
required:true 表示参数必输,false 标识参数非必输,默认为非必输
此注解通常与 @RequestParam 或者 @PathVariable 集合使用,因为它的作用只是定义每个参数(因此可以不使用,但是为了方便测试,加上效果更好),如果要获取前端的参数还需要通过 @RequestParam 或者 @PathVariable 来获取

3.2.4 @ApiImplicitParams 注解和 @ApiImplicitParam 注解

定义参数的注解除了 @ApiParam 之外,这两个注解也可以定义参数
①、@ApiImplicitParams 定义一组参数
②、@ApiImplicitParam 写在 @ApiImplicitParams 中,定义每个参数的信息,属性为:
name:参数英文名称
value:参数中文名称
paramType:调用的 url 参数形式,“query”为问号"?"后面的拼接参数,“path”为绑定的参数

此时,项目已经大概集成完毕,运行项目,看看效果: http://localhost:8080/swagger-ui.html

20191108104413.png

点开相应的接口,可以做相应的测试。

http://localhost:8080/v2/api-docs?group=v1-version-api 效果如下:

{
    "swagger": "2.0",
    "info": {
        "description": "测试项目API文档",
        "version": "1.0.0",
        "title": "测试API",
        "termsOfService": "http://blog.kangaroohy.top",
        "contact": {
            "name": "kangaroo1122",
            "url": "http://blog.kangaroohy.top",
            "email": "326170945@qq.com"
        },
        "license": {
            "name": "MIT",
            "url": "https://mit-license.org/"
        }
    },
    "host": "localhost:8080",
    "basePath": "/",
    "tags": [
        {
            "name": "Test接口",
            "description": "Test Controller"
        }
    ],
    "paths": {
        "/v1/test1": {
            "get": {
                "tags": [
                    "Test接口"
                ],
                "summary": "测试url-test1",
                "description": "用于获取测试数据",
                "operationId": "test1UsingGET",
                "produces": [
                    "*/*",
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "password",
                        "in": "query",
                        "description": "密码",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "name": "username",
                        "in": "query",
                        "description": "姓名",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "type": "string"
                        }
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "deprecated": false,
                "x-order": "1"
            }
        }
    }
}

3.3 配置 swagger2markup

需要添加的依赖和插件在 3.1 已经添加好了,接下来是新建一个导出文档的配置类:ExportConfig.java

@RunWith(SpringRunner.class)
public class ExportConfig {
    /**
     * 生成AsciiDocs格式文档
     * @throws Exception
     */
    @Test
    public void generateAsciiDocs() throws Exception {
        //    输出Ascii格式
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC)
                .withOutputLanguage(Language.ZH)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8080/v2/api-docs?group=v1-version-api"))
                .withConfig(config)
                .build()
                .toFolder(Paths.get("src/docs/asciidoc/generated"));
    }
    /**
     * 生成Markdown格式文档
     * @throws Exception
     */
    @Test
    public void generateMarkdownDocs() throws Exception {
        //    输出Markdown格式
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.MARKDOWN)
                .withOutputLanguage(Language.ZH)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8080/v2/api-docs?group=v1-version-api"))
                .withConfig(config)
                .build()
                .toFolder(Paths.get("src/docs/markdown/generated"));
    }
    /**
     * 生成Confluence格式文档
     * @throws Exception
     */
    @Test
    public void generateConfluenceDocs() throws Exception {
        //    输出Confluence使用的格式
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.CONFLUENCE_MARKUP)
                .withOutputLanguage(Language.ZH)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8080/v2/api-docs?group=v1-version-api"))
                .withConfig(config)
                .build()
                .toFolder(Paths.get("src/docs/confluence/generated"));
    }
    /**
     * 生成AsciiDocs格式文档,并汇总成一个文件
     * @throws Exception
     */
    @Test
    public void generateAsciiDocsToFile() throws Exception {
        //    输出Ascii到单文件
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC)
                .withOutputLanguage(Language.ZH)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8080/v2/api-docs?group=v1-version-api"))
                .withConfig(config)
                .build()
                .toFile(Paths.get("src/docs/asciidoc/generated/all"));
    }
    /**
     * 生成Markdown格式文档,并汇总成一个文件
     * @throws Exception
     */
    @Test
    public void generateMarkdownDocsToFile() throws Exception {
        //    输出Markdown到单文件
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.MARKDOWN)
                .withOutputLanguage(Language.ZH)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8080/v2/api-docs?group=v1-version-api"))
                .withConfig(config)
                .build()
                .toFile(Paths.get("src/docs/markdown/generated/all"));
    }
}

在 idea 中配置一个 maven 命令 asciidoctor:process-asciidoc,方便执行(也可不用配置,每次需要导出时手动输入运行)

20191108105845.png

3.4 导出 html 和 markdown 文档

首先运行 ExportConfig.java 中的 generateAsciiDocsToFile() 方法,将文档生成到一个总的 AsciiDocs 格式文档,再执行之前配置的 maven 命令,生成 html 文档,完成之后,打开项目下 docs 文件,便可以看到生成的文档。

同理,运行 generateMarkdownDocsToFile() 将生成一个总的 markdown 格式的文档,其他的可以自测。

20191108110405.png

4 集成 knife4j

虽然 swagger-ui 能完成前端文档的展示和调试,但是界面不是很友好,为此,引入 knife4j,优点就不说了,具体看官方文档。

4.1 依赖

删除官方的 swagger-ui,并引入 knife4j,其他的依赖不变

	<dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>1.9.6</version>
        </dependency>

4.2 配置

在 swagger 的配置类 SwaggerConfig.java 上加一个注解:@EnableSwaggerBootstrapUi,值得注意的是,这个注解的最后一个字母是小写 i,如果改成大写 I,则只是给前端文档换一个皮肤,不能使用增强功能(排序 等)

20191108121527.png

此时,访问 http://{IP}:{端口}/{项目名}/doc.html,即:http://localhost:8080/doc.html(我这里 IEAD 运行项目时没有添加项目名,这个根据自己设定来。)效果如下:

20191108121915.png

4.3 其他设置

项目接口排序,有两种排序的方式,具体操作,官方说的很清楚,直接上链接:接口排序

  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 12 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    76 引用 • 421 回帖
  • 后端
    45 引用 • 126 回帖 • 1 关注
  • knife4j
    4 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...