使用Spring REST Docs创建API文档
(这是在佳都科技实习时导师安排的学习整理的笔记。)
PS:可参考官网:https://spring.io/projects/spring-restdocs
1.创建项目 SpringBoot (maven)
首先创建一个SpringBoot的maven工程。创建好基本的controller层。
pom.xml
项目结构
2.加入对Spring REST Docs的支持
依赖
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Spring REST Docs-->
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<version>2.0.3.RELEASE</version>
<scope>test</scope>
</dependency>
插件
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
</dependencies>
</plugin>
3.编写好controller层的Junit单元测试
BaseControllerTest.class
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class BaseControllerTest<T> {
private Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];//获取泛型T的类
private List<FieldDescriptor> fieldDescriptors = new ArrayList<>();//泛型T的字段说明
private List<FieldDescriptor> objectResultFieldDescriptors = new ArrayList<>();//返回结果的data为Object的字段说明
private List<FieldDescriptor> booleanResultFieldDescriptors = new ArrayList<>();//返回结果的data为Boolean的字段说明
private MockMvc mockMvc;
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@Autowired
private WebApplicationContext context;
@Autowired
private ObjectMapper objectMapper;
//定义接口文档输出的位置格式
private final String documentLocation = "{ClassName}/{methodName}";
/**
* 初始化
*/
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
{
for (Field declaredField : entityClass.getDeclaredFields()) {
fieldDescriptors.add(fieldWithPath(declaredField.getName()).description(declaredField.getAnnotation(ApiModelProperty.class).value()));
}
}
{
objectResultFieldDescriptors.add(fieldWithPath("code").description("业务码"));
objectResultFieldDescriptors.add(fieldWithPath("msg").optional().type(JsonFieldType.STRING).description("业务描述"));
objectResultFieldDescriptors.add(subsectionWithPath("data").description("数据"));//data存在子节点
}
{
booleanResultFieldDescriptors.add(fieldWithPath("code").description("业务码"));
booleanResultFieldDescriptors.add(fieldWithPath("msg").optional().type(JsonFieldType.STRING).description("业务描述"));
booleanResultFieldDescriptors.add(fieldWithPath("data").description("是否成功"));//data不存在子节点
}
}
/**
* 测试类通过自定义的List<Snippet>的来控制接口的数据
* 打印请求结果,同时生成接口文档
*
* @param resultActions
* @param snippets
* @return
* @throws Exception
*/
public MvcResult afterRequest(ResultActions resultActions, List<Snippet> snippets) throws Exception {
//为传进来的snippets在前面加上返回结果的字段说明
snippets.add(0, relaxedResponseFields(
objectResultFieldDescriptors
));
return resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andDo(
document(documentLocation,
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
snippets.toArray(new Snippet[0]))
)
.andReturn();
}
public MockMvc getMockMvc() {
return mockMvc;
}
public List<FieldDescriptor> getFieldDescriptors() {
return fieldDescriptors;
}
public List<FieldDescriptor> getObjectResultFieldDescriptors() {
return objectResultFieldDescriptors;
}
public List<FieldDescriptor> getBooleanResultFieldDescriptors() {
return booleanResultFieldDescriptors;
}
public ObjectMapper getObjectMapper() {
return objectMapper;
}
}
UserControllerTest .class
public class UserControllerTest extends BaseControllerTest<User> {
/**
* 根据用户id获取用户信息
*
* @throws Exception
*/
@Test
public void getById() throws Exception {
List<FieldDescriptor> roleFieldDescriptors = new ArrayList<>();
for (Field declaredField : Role.class.getDeclaredFields()) {
roleFieldDescriptors.add(fieldWithPath(declaredField.getName()).description(declaredField.getAnnotation(ApiModelProperty.class).value()));
}
List<Snippet> snippets = new ArrayList<>();
snippets.add(requestParameters(parameterWithName("id").description("用户id")));
snippets.add(relaxedResponseFields(
beneathPath("data").withSubsectionId("data"),
getFieldDescriptors()
));
//如果返回的data.roles存在数据,可以这样输出到roles文件中
// snippets.add(relaxedResponseFields(
// beneathPath("data.roles").withSubsectionId("roles"),
// roleFieldDescriptors
// ));
Long id = 1L;
MvcResult mvcResult = afterRequest(
getMockMvc().perform(MockMvcRequestBuilders.get("/user/getById")
.param("id", id.toString())),
snippets
);
}
}
4.运行测试类后会生成如下文件
5.adoc文件转html文件
在main创建如下文件夹和文件(文件名任意)
内容
= API
:toc: left
:toclevels: 4
:toc-title: 接口目录
[[user]]
== 1.用户管理
[[user-getById]]
=== 根据用户id获取用户信息
operation::UserControllerTest/getById[]
运行mvn package或点击如下位置
6.结果显示
成功后在如下文件夹有结果
结果显示
7.总结
可能刚接触,体验感一般。需要在测试代码中写上关于字段说明的代码才会显示字段信息。由于返回的字段多,这里用了泛型+反射的方式
因为项目用到了Swagger,所以通过反射拿到字段的注解说明来进行描述。
同时需要注意的是如果response的字段是一个Object(即存在子节点),可以通过如下方式获取。
就先这样吧。