用Api2Doc代替Swagger2生成 Restful API 文档

背景介绍

在进行前后端分离式开发项目过程中,需要有效的沟通。接口文档因为更新的不及时,也难免存在错误,使沟通的成本大大增加。因此,业界就出现了一些根据代码自动生成 Restful API 文档的开源项目,与 Spring Boot 结合比较好的是 Swagger2,Swagger2 通过读取 Controller代码中的注解信息,来自动生成 API 文档,可以节省大量的手工编写文档的工作量。我之前也是用的 Swagger2,但发现 Swagger2 也有好多地方用得不爽,如注解非常臃肿、页面排版不太友好。想学习使用Swagger2的请参考Spring-Boot-项目中使用Swagger2。Api2Doc 专注于 Restful API 文档的自动生成,它的原理与 Swagger2 是类似的,都是通过反射,分析 Controller 中的信息生成文档,但它要比 Swagger2 好很多,最大的不同是Api2Doc 比 Swagger2 要少写很多代码。

使用Api2Doc

创建SpringBoot工程

具体创建步骤略,可参考使用STS创建Spring-Boot-项目

在工程中引入Maven依赖

<dependency>
  <groupId>com.github.terran4j</groupId>
  <artifactId>terran4j-commons-api2doc</artifactId>
  <version>1.0.2</version>
</dependency>

启用 Api2Doc 服务

在有 @SpringBootApplication 注解的类上,添加 @EnableApi2Doc
注解,以启用 Api2Doc 服务。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.terran4j.commons.api2doc.config.EnableApi2Doc;
@EnableApi2Doc
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

具体示例

给 Controller 类上添加文档注解

package cn.com.yd.exam.controller;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.terran4j.commons.api2doc.annotations.Api2Doc;
import com.terran4j.commons.api2doc.annotations.ApiComment;
import com.terran4j.commons.api2doc.annotations.ApiError;
import cn.com.yd.exam.bean.User;
import cn.com.yd.exam.bean.UserType;

@Api2Doc(id = "demo1", name = "用户接口", order = 1)
@ApiComment(seeClass = User.class)
@RestController
@RequestMapping(value = "/apis/v1/demo/users")
public class UserController {
    @Api2Doc(order = 1)
    @ApiComment("添加一个新的用户。")
    @ApiError(value = "user.exists", comment = "此用户已经存在!")
    @PostMapping(name = "新增用户",value="")
    public User addUser(
            @ApiComment("用户所在部门名称") @RequestParam(required = true) String dept, 
            @ApiComment("用户名称") @RequestParam(required = true) String name, 
            @ApiComment("用户密码") @RequestParam(required = true) String password, 
            @ApiComment("用户类型") @RequestParam(required = true) UserType type) {
        User user = new User();
        user.setDept(dept).setName(name).setPassword(password).setType(type);
        return user; // TODO: 还未实现。
    }

    @Api2Doc(order = 2)
    @ApiComment("根据用户id,删除指定的用户")
    @ApiError(value = "user.not.found", comment = "此用户不存在!")
    @ApiError(value = "admin.cant.delete", comment = "不允许删除管理员用户!")
    @DeleteMapping(name = "删除指定用户", value = "/{id}")
    public void delete(@PathVariable("id") Long id) {

    }

    @Api2Doc(order = 3)
    @ApiComment("根据用户id,查询此用户的信息")
    @ApiError(value = "user.not.found", comment = "此用户不存在!")
    @GetMapping(name = "查询单个用户", value = "{id}")
    public User getUser(@PathVariable("id") Long id) {
        return null; // TODO: 还未实现。
    }

    @Api2Doc(order = 4)
    @ApiComment("查询所有用户,按注册时间进行排序。")
    @GetMapping(name = "查询用户列表",value="")
    public List<User> getUsers() {
        return null; // TODO: 还未实现。
    }
}

User类定义

package cn.com.yd.exam.bean;
import java.util.Date;
import com.terran4j.commons.api2doc.annotations.ApiComment;
import com.terran4j.commons.restpack.RestPackIgnore;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class User {
    @ApiComment(value = "用户id", sample = "123")
    private Long id;

    @ApiComment(value = "用户名", sample = "terran4j")
    private String name;

    @ApiComment(value = "账号密码,字母与数字的组合,区分大小写,8-12位", sample = "sdfi23skvs")
    private String password;

    @ApiComment(value = "用户所在的部门", sample = "研发组")
    private String dept;

    @ApiComment(value = "用户类型", sample = "admin")
    private UserType type;

    @ApiComment(value = "是否已删除", sample = "true")
    @RestPackIgnore
    private Boolean deleted;

    @ApiComment(value = "创建时间,也是注册时间。",sample="2018-12-12")
    private Date createTime;
}

UserType枚举定义

package cn.com.yd.exam.bean;
import com.terran4j.commons.api2doc.annotations.ApiComment;

public enum UserType {
    @ApiComment("管理员")
    admin,

    @ApiComment("普通用户")
    user
}

运行效果

在浏览器中输入http://localhost:8080/api2doc/home.html,即可访问ApiDoc接口文档,如下图

api2doc.png

一些细节的设置

设置接口文档的标题

可在application.properties中进行接口文档的标题和图标的设置,图标为一个全路径 URL,或本站点相对路径 URL 都行。

# 中文标题出现乱码的问题,故此设置成英文的了
api2doc.title=Financial Information System APIs Document
# 图标为一个全路径 URL,或本站点相对路径 URL 都行
api2doc.icon=https://spring.io/img/homepage/icon-spring-framework.svg

开启和关闭 Api2Doc 服务

由于Api2Doc服务没有访问权限校验,建议仅在受信任的网络环境如公司内网中才启用 Api2Doc 服务。可在application.properties中配置api2doc.enabled属性,以开启或关闭 Api2Doc 服务,api2doc.enabled=true或者不写表示启用。

api2doc.enabled=false

定制欢迎页面

每次访问文档页面http://localhost:8080/api2doc/home.html 时,
中间的内容是非常简单的一句:

欢迎使用 Api2Doc !

这似乎有点不太好,我们可以编写自己的欢迎页。
方法很简单,在 src/main/resources 目录下创建api2doc 目录,然后在api2doc目录下创建一个名为
welcome.md 的文件(这个名称是固定的),然后用 md 语法编写内容就可以。

给文档菜单项排序

可以用@Api2Doc中的order属性给菜单项排序,order的值越小该菜单项就越排在前面。@Api2Doc既可以用在类上又可以用在方法上。

@Api2Doc(order = 4)

Api2Doc注解详解

@Api2Doc

@Api2Doc 用于对文档的生成进行控制。
@Api2Doc 修饰在类上,表示这个类会参与到文档生成过程中,Api2Doc 服务会扫描 Spring 容器中所有的 Controller 类,只有类上有 @Api2Doc 的类,才会被生成文档,一个类对应于文档页面左侧的一级菜单项,@Api2Doc的name 属性则表示这个菜单项的名称。
@Api2Doc 也可以修饰在方法,不过在方法上的 @Api2Doc 通常是可以省略,Api2Doc服务会扫描这个类的所有带有@RequestMapping的方法,每个这样的方法对应文档页面的左侧的二级菜单项,菜单项的名称取@RequestMapping的name属性,当然您仍然可以在方法上用 @Api2Doc的name属性进行重定义。

@ApiComment

@ApiComment用于对API进行说明,它可以修饰在很多地方:
修饰在类上,表示对这组API接口进行说明;
修饰在方法上,表示对这个API接口进行说明;
修饰在参数上,表示对这个API接口的请求参数进行说明;
修饰在返回类型的属性上,表示对这个API接口的返回字段进行说明;
修饰在枚举项上,表示对枚举项进行说明;
如果相同名称、相同意义的属性或参数字段,其说明已经在别的地方定义过了,
可以用 @ApiComment的seeClass属性表示采用指定类的同名字段上的说明信息。

@ApiError

@ApiError用于定义错误码,有的API方法在执行业务逻辑时会产生错误,出错后会在返回报文包含错误码,以方便客户端根据错误码作进一步的处理,因此也需要在API文档上体现错误码的说明。

Api2Doc的缺点

Api2Doc的缺点是不能像Swagger那样在页面中进行测试,不过可以借助其他的工具进行测试。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容