场景
有时候需要为前端开发者提供Restful Api说明文档,通过word文档创建和修改非常耗时,希望有一种比较便捷的第三方库可以减少生成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引入下面依赖:
io.springfox
springfox-swagger2
2.4.0
test
- 配置一个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.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", "[email protected]"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("1.0.0")
.build();
}
}
- 运行Spring项目
运行项目后会发布Spring的路由器多了若干个对外的接口,其中一个是/v2/api-docs/
。
通过这个接口可以获取到由Swagger生成的所有API接口的元数据。 - Api界面部署
呈现由Swagger生成的API大概有两种方法(目前只找到两种)
- Swagger自带有Swagger-UI可以直观显示API接口说明并可以在线调试
- 使用Swagger2Markerup插件和AsciiDoc插件可以将Swagger生成的JSON元数据转换成HTML、PDF
注意:springfox-swagger2是依赖与Spring MVC框架的!!
Swagger-UI部署
- 引入Swagger-UI依赖
io.springfox
springfox-swagger-ui
2.5.0
- 运行Spring项目,并访问htttp://[host]:[ip]/swagger-ui.html
注意:必须首先部署Swagger
Swagger2Markerup与AsciiDoc插件部署
- maven插件部署
io.github.swagger2markup
swagger2markup-maven-plugin
${swagger2markup.version}
io.github.swagger2markup
swagger2markup-import-files-ext
${swagger2markup.version}
io.github.swagger2markup
swagger2markup-spring-restdocs-ext
${swagger2markup.version}
${swagger.input}
${generated.asciidoc.directory}
ASCIIDOC
TAGS ${project.basedir}/src/docs/asciidoc/extensions/overview
${project.basedir}/src/docs/asciidoc/extensions/definitions
${project.basedir}/src/docs/asciidoc/extensions/paths
${project.basedir}src/docs/asciidoc/extensions/security/
${swagger.snippetOutput.dir}
false
test
convertSwagger2markup
org.asciidoctor
asciidoctor-maven-plugin
1.5.3
org.asciidoctor
asciidoctorj-pdf
1.5.0-alpha.10.1
${asciidoctor.input.directory}
index.adoc
book
left
3
${generated.asciidoc.directory}
output-html
test
process-asciidoc
html5
${asciidoctor.html.output.directory}
- 编写文档生成脚本
@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依赖
io.springfox
springfox-swagger2
2.5.0
test
io.github.swagger2markup
swagger2markup-maven-plugin
1.0.1
test
org.asciidoctor
asciidoctorj
1.5.4.1
test
- 编写文档生成脚本
@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动态向adoc文档添加额外内容。
插件使用
- maven插件部署
swagger2markup-import-files-ext是可以与swagger2markup-maven-plugin插件一起使用的
io.github.swagger2markup
swagger2markup-maven-plugin
${swagger2markup.version}
io.github.swagger2markup
swagger2markup-import-files-ext
${swagger2markup.version}
io.github.swagger2markup
swagger2markup-spring-restdocs-ext
${swagger2markup.version}
${swagger.input}
${generated.asciidoc.directory}
ASCIIDOC
TAGS
${project.basedir}/src/docs/asciidoc/extensions/overview
${project.basedir}/src/docs/asciidoc/extensions/definitions
${project.basedir}/src/docs/asciidoc/extensions/paths
${project.basedir}src/docs/asciidoc/extensions/security/
${swagger.snippetOutput.dir}
false
test
convertSwagger2markup
swagger2markup.extensions.dynamicOverview.contentPath
swagger2markup.extensions.dynamicDefinitions.contentPath
swagger2markup.extensions.dynamicPaths.contentPath
swagger2markup.extensions.dynamicSecurity.contentPath
四个参数是必须要指定的,意思是swagger2markup-maven-plugin启动时,会在上述四个目录中分别查找adoc,并把adoc内容分别插入到原adoc中。
- 创建自定义的adc
完成步骤1部署后,我们需要定义每一个adoc的内容及其插入的位置。Swagger2Markup文档中定义下面规则用于将自定义的adoc插入到接口文档指定位置中:
All extensions, relatively to each extension contentPath :
DOCUMENT_BEFORE : document-before-..
DOCUMENT_BEGIN : document-begin-
DOCUMENT_END : document-end-..
DOCUMENT_AFTER : document-after-
Paths extensions, relatively to each extension contentPath :
OPERATION_BEFORE :/operation-before-. .
OPERATION_BEGIN :/operation-begin-
OPERATION_END :/operation-end-. .
OPERATION_AFTER :/operation-after-
OPERATION_DESCRIPTION_BEFORE:/operation-description-before-. .
OPERATION_DESCRIPTION_BEGIN:/operation-description-begin-
OPERATION_DESCRIPTION_END:/operation-description-end-. .
OPERATION_DESCRIPTION_AFTER:/operation-description-after-
OPERATION_PARAMETERS_BEFORE:/operation-parameters-before-. .
OPERATION_PARAMETERS_BEGIN:/operation-parameters-begin-
OPERATION_PARAMETERS_END:/operation-parameters-end-. .
OPERATION_PARAMETERS_AFTER:/operation-parameters-after-
OPERATION_RESPONSES_BEFORE:/operation-responses-before-. .
OPERATION_RESPONSES_BEGIN:/operation-responses-begin-
OPERATION_RESPONSES_END:/operation-responses-end-. .
OPERATION_RESPONSES_AFTER:/operation-responses-after-
OPERATION_SECURITY_BEFORE:/operation-security-before-. .
OPERATION_SECURITY_BEGIN:/operation-security-begin
OPERATION_SECURITY_END:/operation-security-end-. .
OPERATION_SECURITY_AFTER:/operation-security-after-
Definitions extensions, relatively to each extension contentPath :
DEFINITION_BEFORE :/definition-before-. .
DEFINITION_BEGIN :/definition-begin-
DEFINITION_END :/definition-end-. .
DEFINITION_AFTER :/definition-after-
Security extensions, relatively to each extension contentPath :
SECURITY_SCHEME_BEFORE :/security-scheme-before-. .
SECURITY_SCHEME_BEGIN :/security-scheme-begin-
SECURITY_SCHEME_END :/security-scheme-end-. .
SECURITY_SCHEME_AFTER :/security-scheme-after-
注意:operationId相当于@ApiOperation注释中的nickname属性
假设现在要将一段接口说明插入到operationId为findPet的接口中,我们需要做如下步骤:
- 创建一个新目录src/docs/asciidoc/extensions/paths/
- 在上述目录下创建一个
findPet
的子目录 - 创建一个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插件部署
这里我们使用与上一个述件不同的部署方式,直接到通过代码配置插件
io.github.swagger2markup
swagger2markup-spring-restdocs-ext
1.0.0
compile
- 配置
Map 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,需要手工创建插件并通过withExplicitSnippets自行设置扫描的文件名。
其他参考资料
Spring Boot Test
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html