大致流程:

注意:图片和引用文件的位置一定要正确设置,不然渲染不出内容。

1. 新建springboot项目

推荐使用spring 提供的 生成器,可以避免很多坑,把需要的依赖及版本都配置好了,效果如下图。

图片.png
Figure 1. 图片.png

2. 准备工作

需求: 一个用户注册接口,包含昵称、账号以及密码,简单即可。

实现: 这里只做controller,模拟返回成功,service以及数据库就忽略。

编辑器: IntelliJ IDEA 2021.1.2 (Community Edition)

JDK: 1.8

OS: windows 10

lombok: 一个插件,可以通过注解生成getter、setter等,不需要深入研究。

3. src/main/java

新建一个UserController、UserVO、UserService,如下:

图片.png
Figure 2. 图片.png

UerVO: 用户注册参数

package com.wanghengzhi.learn.springrestdoc.module.user.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用户注册参数
 *
 * @author wanghengzhi
 * @since 2021/8/7 8:55
 */
package com.wanghengzhi.learn.springrestdoc.module.user.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用户注册参数
 *
 * @author wanghengzhi
 * @since 2021/8/7 8:55
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVO {
    /**
     * 昵称
     */
    private String nickName;
    /**
     * 账号
     */
    private String account;
    /**
     * 密码
     */
    private String password;
}

UserController:加入一个注册接口

package com.wanghengzhi.learn.springrestdoc.module.user;

import com.wanghengzhi.learn.springrestdoc.module.user.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户操作接口
 *
 * @author wanghengzhi
 * @since 2021/8/7 8:54
 */
@RestController
@SuppressWarnings("unused")
public class UserController {

    public static final String URL_USER_REGISTER = "/user/register";

    @Autowired
    private UserService userService;

    /**
     * 用户注册接口
     * @param userVO 用户输入参数
     * @return 注册结果
     */
    @PostMapping(URL_USER_REGISTER)
    public ResponseEntity<Object> register(@RequestBody UserVO userVO) {
        // 调用 service 进行保存
        userService.register(userVO);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

UserService: 业务处理类,此例中不需要实现。

package com.wanghengzhi.learn.springrestdoc.module.user;

import com.wanghengzhi.learn.springrestdoc.module.user.vo.UserVO;
import org.springframework.stereotype.Service;

/**
 * 用户操作 service 层
 *
 * @author wanghengzhi
 * @since 2021/8/7 9:01
 */
@Service
public class UserService {

    @SuppressWarnings("unused")
    public void register(UserVO userVO) {
        // 省略业务逻辑。
    }

}

4. src/main/test

图片.png
Figure 3. 图片.png

只需要新增一个`UserControllerTest`

4.1 配置 Spring Rest Doc 输出以及处理方式

MyRestDocsConfiguration: 这里配置文档格式化输出,其他配置,可自行研究。

package com.wanghengzhi.tdd.config;

import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.boot.test.context.TestConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;

/**
 * 配置 spring rest doc
 * @author wanghengzhi
 * @since 2021/8/6 23:02
 */
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {

    @Override
    public void customize(MockMvcRestDocumentationConfigurer configurer) {
        configurer.operationPreprocessors().withRequestDefaults(prettyPrint()).withResponseDefaults(prettyPrint());
    }

}

MyResultHandlerConfiguration: 配置了生成的文件名格式。

package com.wanghengzhi.learn.springrestdoc.config;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;

/**
 * 配置文档处理
 * @author wanghengzhi
 * @since 2021/8/6 23:03
 */
@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {

    @Bean
    public RestDocumentationResultHandler restDocumentation() {
        return MockMvcRestDocumentation.document("{class_name}/{method_name}");
    }

}

4.2 编写测试,并生成文档

package com.wanghengzhi.learn.springrestdoc.module.user;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wanghengzhi.learn.springrestdoc.config.MyRestDocsConfiguration;
import com.wanghengzhi.learn.springrestdoc.config.MyResultHandlerConfiguration;
import com.wanghengzhi.learn.springrestdoc.module.user.vo.UserVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.restdocs.headers.HeaderDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.payload.PayloadDocumentation;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 测试
 *
 * Annotation {@code @WebMvcTest} :
 *      只测是Controller层代码,service层依赖需要使用{@code @MockBean}注入。
 * Annotation {@code @AutoConfigureRestDocs}:
 *      这里配置了生成snippets位置,默认放在target目录下,由于每次clean会删除。
 *      手动编写的*。adoc文档会找不到,会报错。如果多人开发,别人拉取代码,如果不运行test,编辑器会提示错误。
 * Annotation {@code @Import}:
 *      手动导入自定义的配置文件。包含文档生成规则配置,以及生成一个handler来处理文档。
 *
 * @author wanghengzhi
 * @since 2021.8.7
 */
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs(outputDir = "src/main/asciidoc/snippets")
@Import({MyRestDocsConfiguration.class, MyResultHandlerConfiguration.class})
class UserControllerTest {

    @Autowired
    private ObjectMapper objectMapper;
    @MockBean
    private UserService userService;
    @Autowired
    private MockMvc mockMvc;
    /**
     * 统一使用这个handler生成文档。
     */
    @Autowired
    private RestDocumentationResultHandler resultHandler;

    @Test
    void register() throws Exception {
        UserVO userVO = new UserVO();
        userVO.setAccount("my_account");
        userVO.setNickName("my_nickName");
        userVO.setPassword("my_password");
        String token = "My Header";
        MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post(UserController.URL_USER_REGISTER)
                .accept(MediaType.APPLICATION_JSON)
                .header(token, "My_Header_value")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(userVO));

        ResultActions perform = mockMvc.perform(request);
        perform.andReturn().getResponse().setCharacterEncoding("utf-8");

        perform.andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
        .andDo(resultHandler.document(
                HeaderDocumentation.requestHeaders(
                        HeaderDocumentation.headerWithName(token).description("自定义header").optional()
                ),
                PayloadDocumentation.requestFields(
                        PayloadDocumentation.fieldWithPath("nickName").type(JsonFieldType.STRING).description("用户昵称"),
                        PayloadDocumentation.fieldWithPath("account").type(JsonFieldType.STRING).description("用户账号"),
                        PayloadDocumentation.fieldWithPath("password").type(JsonFieldType.STRING).description("密码")
                ),
        PayloadDocumentation.responseFields(
                        PayloadDocumentation.fieldWithPath("ok").type(JsonFieldType.BOOLEAN).description("是否成功"),
                        PayloadDocumentation.fieldWithPath("message").type(JsonFieldType.STRING).description("信息")
                )));
    }
}

5. 运行测试,并生成结果

图片.png
Figure 4. 图片.png

红箭头标识的是,是测试类手动定义生成的,其他都是默认生成的文件。

6 编写测试文档adoc,并生成对应的html文件

  • 新建 index.adoc,使用 ascciidoctor语法

  • 根据需求格式进行排版,生成的snippets文件引用。

  • IDea有插件可以进行预览,也可以生成html

图片.png
Figure 5. 图片.png

6. 补充说明

可以通过配置asciidoc-plugin,在指定maven目标中可以生成html。

备注: 1. <version>1.5.8</version>: 可以根据喜好选择版本。 2. <executions>...</executions>: 执行配置, 可多个,用于不同条件下的执行内容。 3. <execution>...</execution>: 具体执行某个行为 4. <id>generate-docs</id>:唯一即可,可自由命名 5. <phase>prepare-package</phase>:前置条件:在maven package前执行 6. <goal>process-asciidoc</goal>: 目的,可设置多个,这里执行process-asciidoc(处理.adoc文件为html5)。这是asciidoc插件的命令。见下图。

图片.png
Figure 6. 图片.png

7. 附表

document:

关键字 作用 类名

HeaderDocumentation

记录header中的参数

org.springframework.restdocs.headers.HeaderDocumentation

PayloadDocumentation

记录body中的参数

org.springframework.restdocs.payload.PayloadDocumentation

RequestDocumentation

org.springframework.restdocs.request.RequestDocumentation