使用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(即存在子節點),可以通過如下方式獲取。
就先這樣吧。