場景
有時候需要為前端開發(fā)者提供Restful Api說明文檔,通過word文檔創(chuàng)建和修改非常耗時,希望有一種比較便捷的第三方庫可以減少生成Api說明文檔的工作量
基于Spring的Restful Api生成工具
術語解析
- Springfox-swagger2
Swagger是一個可以生成基于多種語言編寫的Restful Api的文檔生成工具,詳見這里。
查看Springfox-swagger2注解文檔,請點擊這里 - Swagger2markup
Swagger2markup是一個使用java編寫的將Swagger語義轉換成markdown、asciidoc文本格式的開源項目
Swagger2Markerup的詳細說明請見這里 - Asciidoc
AsciiDoc是一種MarkDown的擴展文本格式,AsciiDoc相比MarkDown更適合編寫類似API文檔,學術文檔這樣的文檔。
詳見這里 - Asciidoctor
asciidoctor是一個由ruby編寫的可以將 asciidoc轉換成html、pdf的開源項目,這個項目有java版本和maven插件,詳見這里
接口生成原理
- generate an up-to-date Swagger JSON file during an unit or integration test
使用Springfox-swagger2生成swagger json文件 - convert the Swagger JSON file into AsciiDoc
使用Swagger2markup將swagger json文件轉換成asciidoc文檔片段 - add hand-written AsciiDoc documentation
編寫asciidoc的文檔(主要是組裝步驟2中生成的asciidoc文檔片段) - convert AsciiDoc into HTML and PDF
使用Asciidoctor將asciidoc轉換成HTML 或pdf
Swagger部署說明
- 在pom引入下面依賴:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
- 配置一個SwaggerConfig
@EnableSwagger2
@Configuration
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {
@Bean
public Docket restApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.securitySchemes(asList(
new OAuth(
"petstore_auth",
asList(new AuthorizationScope("write_pets", "modify pets in your account"),
new AuthorizationScope("read_pets", "read your pets")),
Arrays.<GrantType>asList(new ImplicitGrant(new LoginEndpoint("http://petstore.swagger.io/api/oauth/dialog"), "tokenName"))
),
new ApiKey("api_key", "api_key", "header")
))
.select()
.paths(Predicates.and(ant("/**"), Predicates.not(ant("/error")), Predicates.not(ant("/management/**")), Predicates.not(ant("/management*"))))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger Petstore")
.description("Petstore API Description")
.contact(new Contact("TestName", "http:/test-url.com", "test@test.de"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("1.0.0")
.build();
}
}
- 運行Spring項目
運行項目后會發(fā)布Spring的路由器多了若干個對外的接口,其中一個是/v2/api-docs/。
通過這個接口可以獲取到由Swagger生成的所有API接口的元數據。 - Api界面部署
呈現由Swagger生成的API大概有兩種方法(目前只找到兩種)
- Swagger自帶有Swagger-UI可以直觀顯示API接口說明并可以在線調試
- 使用Swagger2Markerup插件和AsciiDoc插件可以將Swagger生成的JSON元數據轉換成HTML、PDF
注意:springfox-swagger2是依賴與Spring MVC框架的?。?/p>
Swagger-UI部署
- 引入Swagger-UI依賴
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.5.0</version>
</dependency>
- 運行Spring項目,并訪問htttp://[host]:[ip]/swagger-ui.html

注意:必須首先部署Swagger
Swagger2Markerup與AsciiDoc插件部署
- maven插件部署
<!-- First, use the swagger2markup plugin to generate asciidoc -->
<plugin>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-maven-plugin</artifactId>
<version>${swagger2markup.version}</version>
<dependencies>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-import-files-ext</artifactId>
<version>${swagger2markup.version}</version>
</dependency>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-spring-restdocs-ext</artifactId>
<version>${swagger2markup.version}</version>
</dependency>
</dependencies>
<configuration>
<swaggerInput>${swagger.input}</swaggerInput>
<outputDir>${generated.asciidoc.directory}</outputDir>
<config>
<swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
<swagger2markup.pathsGroupedBy>TAGS</swagger2markup.pathsGroupedBy> <swagger2markup.extensions.dynamicOverview.contentPath>${project.basedir}/src/docs/asciidoc/extensions/overview</swagger2markup.extensions.dynamicOverview.contentPath>
<swagger2markup.extensions.dynamicDefinitions.contentPath>${project.basedir}/src/docs/asciidoc/extensions/definitions</swagger2markup.extensions.dynamicDefinitions.contentPath>
<swagger2markup.extensions.dynamicPaths.contentPath>${project.basedir}/src/docs/asciidoc/extensions/paths</swagger2markup.extensions.dynamicPaths.contentPath>
<swagger2markup.extensions.dynamicSecurity.contentPath>${project.basedir}src/docs/asciidoc/extensions/security/</swagger2markup.extensions.dynamicSecurity.contentPath>
<swagger2markup.extensions.springRestDocs.snippetBaseUri>${swagger.snippetOutput.dir}</swagger2markup.extensions.springRestDocs.snippetBaseUri>
<swagger2markup.extensions.springRestDocs.defaultSnippets>false</swagger2markup.extensions.springRestDocs.defaultSnippets>
</config>
</configuration>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>convertSwagger2markup</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Run the generated asciidoc through Asciidoctor to generate
other documentation types, such as PDFs or HTML5 -->
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<!-- Include Asciidoctor PDF for pdf generation -->
<dependencies>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj-pdf</artifactId>
<version>1.5.0-alpha.10.1</version>
</dependency>
</dependencies>
<!-- Configure generic document generation settings -->
<configuration>
<sourceDirectory>${asciidoctor.input.directory}</sourceDirectory>
<sourceDocumentName>index.adoc</sourceDocumentName>
<attributes>
<doctype>book</doctype>
<toc>left</toc>
<toclevels>3</toclevels>
<numbered></numbered>
<hardbreaks></hardbreaks>
<sectlinks></sectlinks>
<sectanchors></sectanchors>
<generated>${generated.asciidoc.directory}</generated>
</attributes>
</configuration>
<!-- Since each execution can only handle one backend, run
separate executions for each desired output type -->
<executions>
<execution>
<id>output-html</id>
<phase>test</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html5</backend>
<outputDirectory>${asciidoctor.html.output.directory}</outputDirectory>
</configuration>
</execution>
<!--
<execution>
<id>output-pdf</id>
<phase>test</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>pdf</backend>
<outputDirectory>${asciidoctor.pdf.output.directory}</outputDirectory>
</configuration>
</execution>
-->
</executions>
</plugin>
- 編寫文檔生成腳本
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@AutoConfigureRestDocs(outputDir = "build/asciidoc/snippets")
@SpringBootTest(classes = {Application.class, SwaggerConfig.class})
@AutoConfigureMockMvc
public class Swagger2MarkupTest {
private static final Logger LOG = LoggerFactory.getLogger(Swagger2MarkupTest.class);
@Autowired
private MockMvc mockMvc;
@Test
public void createSpringfoxSwaggerJson() throws Exception {
//String designFirstSwaggerLocation = Swagger2MarkupTest.class.getResource("/swagger.yaml").getPath();
String outputDir = System.getProperty("io.springfox.staticdocs.outputDir");
MvcResult mvcResult = this.mockMvc.perform(get("/v2/api-docs")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
String swaggerJson = response.getContentAsString();
Files.createDirectories(Paths.get(outputDir));
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputDir, "swagger.json"), StandardCharsets.UTF_8)){
writer.write(swaggerJson);
}catch(Exception e){
e.printStackTrace();
}
}
}
注意上述的代碼來源于swagger2markup官方說明一個demo,代碼基于Spring Boot 1.4.0.release
Swagger2Markerup與AsciiDoc無插件調用(推薦)
有時候沒有必須在maven的生命周期中遠行單元測試生成文檔,可以直接代碼塊生成文檔
- maven依賴
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-maven-plugin</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
<version>1.5.4.1</version>
<scope>test</scope>
</dependency>
- 編寫文檔生成腳本
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {BIMobileMasterApplication.class, SwaggerConfig.class})
public class Swagger2MarkupTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void convertSwaggerToAsciiDoc() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/v2/api-docs")
.accept("application/json;charset=utf-8"))
.andExpect(status().isOk())
.andReturn();
//文檔輸出目錄
String outputDirectory = "docs/restful/generated";
Path outputDirectoryPath = Paths.get(outputDirectory);
MockHttpServletResponse response = mvcResult.getResponse();
String swaggerJson = response.getContentAsString();
swaggerJson = swaggerJson.replace("{\"status\":200,\"message\":\"\",\"data\":", "");
swaggerJson = swaggerJson.substring(0,swaggerJson.length()-1);
Swagger2MarkupConverter.from(swaggerJson)
.build()
.toFolder(outputDirectoryPath);
Asciidoctor asciidoctor = Asciidoctor.Factory.create();
Attributes attributes = new Attributes();
attributes.setCopyCss(true);
attributes.setLinkCss(false);
attributes.setSectNumLevels(3);
attributes.setAnchors(true);
attributes.setSectionNumbers(true);
attributes.setHardbreaks(true);
attributes.setTableOfContents(Placement.LEFT);
attributes.setAttribute("generated", "generated");
OptionsBuilder optionsBuilder = OptionsBuilder.options()
.backend("html5")
.docType("book")
.eruby("")
.inPlace(true)
.safe(SafeMode.UNSAFE)
.attributes(attributes);
String asciiInputFile = "docs/restful/index.adoc";
asciidoctor.convertFile(
new File(asciiInputFile),
optionsBuilder.get());
}
Swagger2Markup插件的說明及使用
swagger2markup-import-files-ext
插件說明
有時在寫接口注釋時,可能需要對接口附加一些特別說明,這種情況下Swagger2的Java注釋感覺有點雞肋。 這時可以使用swagger2markup-import-files-ext動態(tài)向adoc文檔添加額外內容。
插件使用
- maven插件部署
swagger2markup-import-files-ext是可以與swagger2markup-maven-plugin插件一起使用的
<plugin>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-maven-plugin</artifactId>
<version>${swagger2markup.version}</version>
<dependencies>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-import-files-ext</artifactId>
<version>${swagger2markup.version}</version>
</dependency>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-spring-restdocs-ext</artifactId>
<version>${swagger2markup.version}</version>
</dependency>
</dependencies>
<configuration>
<swaggerInput>${swagger.input}</swaggerInput>
<outputDir>${generated.asciidoc.directory}</outputDir>
<config>
<swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
<swagger2markup.pathsGroupedBy>TAGS</swagger2markup.pathsGroupedBy>
<swagger2markup.extensions.dynamicOverview.contentPath>${project.basedir}/src/docs/asciidoc/extensions/overview</swagger2markup.extensions.dynamicOverview.contentPath>
<swagger2markup.extensions.dynamicDefinitions.contentPath>${project.basedir}/src/docs/asciidoc/extensions/definitions</swagger2markup.extensions.dynamicDefinitions.contentPath>
<swagger2markup.extensions.dynamicPaths.contentPath>${project.basedir}/src/docs/asciidoc/extensions/paths</swagger2markup.extensions.dynamicPaths.contentPath>
<swagger2markup.extensions.dynamicSecurity.contentPath>${project.basedir}src/docs/asciidoc/extensions/security/</swagger2markup.extensions.dynamicSecurity.contentPath>
<swagger2markup.extensions.springRestDocs.snippetBaseUri>${swagger.snippetOutput.dir}</swagger2markup.extensions.springRestDocs.snippetBaseUri>
<swagger2markup.extensions.springRestDocs.defaultSnippets>false</swagger2markup.extensions.springRestDocs.defaultSnippets>
</config>
</configuration>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>convertSwagger2markup</goal>
</goals>
</execution>
</executions>
</plugin>
swagger2markup.extensions.dynamicOverview.contentPath
swagger2markup.extensions.dynamicDefinitions.contentPath
swagger2markup.extensions.dynamicPaths.contentPath
swagger2markup.extensions.dynamicSecurity.contentPath
四個參數是必須要指定的,意思是swagger2markup-maven-plugin啟動時,會在上述四個目錄中分別查找adoc,并把adoc內容分別插入到原adoc中。
- 創(chuàng)建自定義的adc
完成步驟1部署后,我們需要定義每一個adoc的內容及其插入的位置。Swagger2Markup文檔中定義下面規(guī)則用于將自定義的adoc插入到接口文檔指定位置中:
All extensions, relatively to each extension contentPath :
DOCUMENT_BEFORE : document-before-.<markup.ext>
DOCUMENT_BEGIN : document-begin-.<markup.ext>
DOCUMENT_END : document-end-.<markup.ext>
DOCUMENT_AFTER : document-after-.<markup.ext>
Paths extensions, relatively to each extension contentPath :
OPERATION_BEFORE : <operationId>/operation-before-.<markup.ext>
OPERATION_BEGIN : <operationId>/operation-begin-.<markup.ext>
OPERATION_END : <operationId>/operation-end-.<markup.ext>
OPERATION_AFTER : <operationId>/operation-after-.<markup.ext>
OPERATION_DESCRIPTION_BEFORE: <operationId>/operation-description-before-.<markup.ext>
OPERATION_DESCRIPTION_BEGIN: <operationId>/operation-description-begin-.<markup.ext>
OPERATION_DESCRIPTION_END: <operationId>/operation-description-end-.<markup.ext>
OPERATION_DESCRIPTION_AFTER: <operationId>/operation-description-after-.<markup.ext>
OPERATION_PARAMETERS_BEFORE: <operationId>/operation-parameters-before-.<markup.ext>
OPERATION_PARAMETERS_BEGIN: <operationId>/operation-parameters-begin-.<markup.ext>
OPERATION_PARAMETERS_END: <operationId>/operation-parameters-end-.<markup.ext>
OPERATION_PARAMETERS_AFTER: <operationId>/operation-parameters-after-.<markup.ext>
OPERATION_RESPONSES_BEFORE: <operationId>/operation-responses-before-.<markup.ext>
OPERATION_RESPONSES_BEGIN: <operationId>/operation-responses-begin-.<markup.ext>
OPERATION_RESPONSES_END: <operationId>/operation-responses-end-.<markup.ext>
OPERATION_RESPONSES_AFTER: <operationId>/operation-responses-after-.<markup.ext>
OPERATION_SECURITY_BEFORE: <operationId>/operation-security-before-.<markup.ext>
OPERATION_SECURITY_BEGIN: <operationId>/operation-security-begin.<markup.ext>
OPERATION_SECURITY_END: <operationId>/operation-security-end-.<markup.ext>
OPERATION_SECURITY_AFTER: <operationId>/operation-security-after-.<markup.ext>
Definitions extensions, relatively to each extension contentPath :
DEFINITION_BEFORE : <definitionName>/definition-before-.<markup.ext>
DEFINITION_BEGIN : <definitionName>/definition-begin-.<markup.ext>
DEFINITION_END : <definitionName>/definition-end-.<markup.ext>
DEFINITION_AFTER : <definitionName>/definition-after-.<markup.ext>
Security extensions, relatively to each extension contentPath :
SECURITY_SCHEME_BEFORE : <securitySchemeName>/security-scheme-before-.<markup.ext>
SECURITY_SCHEME_BEGIN : <securitySchemeName>/security-scheme-begin-.<markup.ext>
SECURITY_SCHEME_END : <securitySchemeName>/security-scheme-end-.<markup.ext>
SECURITY_SCHEME_AFTER : <securitySchemeName>/security-scheme-after-.<markup.ext>
注意:operationId相當于@ApiOperation注釋中的nickname屬性
假設現在要將一段接口說明插入到operationId為findPet的接口中,我們需要做如下步驟:
- 創(chuàng)建一個新目錄src/docs/asciidoc/extensions/paths/
- 在上述目錄下創(chuàng)建一個
findPet的子目錄 - 創(chuàng)建一個operation-before-test.adoc文件,內容為:
這是一個插件入adoc
可以看到生成文章后在在findPet的接口前面增加了一段描述

其它的插入位置同理
swagger2markup-spring-restdocs-ext
插件說明
此插件可以自動將Spring rest doc生成的adoc片段添加到Paths說明部分的最后。
Spring Rest doc生成的adoc版片段:
- curl-request.adoc
- http-request.adoc
- http-response.adoc
- httpie-request.adoc
插件默認掃描前3個adoc,當然也可以通過withExplicitSnippets自定義掃描的文件名。
插件使用
- maven插件部署
這里我們使用與上一個述件不同的部署方式,直接到通過代碼配置插件
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-spring-restdocs-ext</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
- 配置
Map<String, String> configMap = new HashMap<>();
configMap.put("swagger2markup.extensions.springRestDocs.snippetBaseUri", "docs/restful/snippets");
configMap.put("swagger2markup.extensions.springRestDocs.defaultSnippets", "true");
configMap.put(Swagger2MarkupProperties.PATHS_GROUPED_BY, GroupBy.TAGS.name());
Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder(configMap)
其中snippetBaseUri是Spring Rest Doc生成文檔的位置;defaultSnippets指明是否使用默認的文件名掃描,如果設置為false,需要手工創(chuàng)建插件并通過withExplicitSnippets自行設置掃描的文件名。
其他參考資料
Spring Boot Test
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html