背景介绍
在进行前后端分离式开发项目过程中,需要有效的沟通。接口文档因为更新的不及时,也难免存在错误,使沟通的成本大大增加。因此,业界就出现了一些根据代码自动生成 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接口文档,如下图
一些细节的设置
设置接口文档的标题
可在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那样在页面中进行测试,不过可以借助其他的工具进行测试。