Swagger 線上可以測試真的很方便, 但畢竟還是要啟動服務, 有時候對外或是對一些非技術人員比較不方便點
還是有個檔案給他讓他方便查看或是寄給對方
不過我們都寫好在註解上了啊, 我們要聰明點, 不要老是在做重複的工作
那今天就教你怎麼把 Swagger 轉成 PDF or Html 文件
官方文件在這裡 Swagger2Markup Documentation
首先我們要增加這個插件 org.asciidoctor.convert
修改 build.gradle
buildscript {
ext {
springBootVersion = '1.5.9.RELEASE'
}
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath "org.asciidoctor:asciidoctor-gradle-plugin:1.5.7"
}
}
apply plugin: 'org.asciidoctor.convert'
jar {
baseName = 'bookservice'
version = '0.0.1-SNAPSHOT'
dependsOn asciidoctor
}
asciidoctor {
dependsOn test
sourceDir 'src/docs/asciidoc'
attributes 'snippets': file("build/asciidoc"),
'doctype': 'book',
'icons': 'font',
'source-highlighter': 'highlightjs',
'toc': 'left',
'toclevels': 2,
'sectlinks': true
outputDir = file('build/asciidoc/generated')
// backends 'html5', 'pdf', 'epub3', 'docbook' , 'revealjs'
// sourceDir = file('src/docs/asciidoc')
// outputDir = file('build/asciidoc/generated')
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('com.h2database:h2')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile 'io.springfox:springfox-swagger2:2.7.0'
compile 'io.springfox:springfox-swagger-ui:2.7.0'
compile 'io.springfox:springfox-data-rest:2.7.0'
// 新增的
testCompile 'io.springfox:springfox-staticdocs:2.6.1'
testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc:2.0.0.RELEASE'
}
準備我們的測試 Swagger2MarkupTest.java, 因為是透過測試的功能在打包之前把文件產出
package com.example.book;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.github.robwin.markup.builder.MarkupLanguage;
import io.github.robwin.swagger2markup.GroupBy;
import io.github.robwin.swagger2markup.Swagger2MarkupConverter;
import org.apache.commons.lang3.Validate;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import springfox.documentation.staticdocs.Swagger2MarkupResultHandler;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BookApplication.class)
public class Swagger2MarkupTest {
@Autowired
private MockMvc mockMvc;
@Before
public void setUp() {
}
// 輸出成 adoc 格式
@Test
public void convertSwaggerToAsciiDoc() throws Exception {
this.mockMvc.perform(get("/v2/api-docs")
.accept(MediaType.APPLICATION_JSON))
// 原本的寫法
.andDo(Swagger2MarkupResultHandler.outputDirectory("src/docs/asciidoc/generated").build())
// 稍微客製化只留下api開頭的path
//.andDo(new PsSwagger2MarkupHandler("src/docs/asciidoc/generated"))
.andExpect(status().isOk());
}
// 輸出成 Markdown 格式
// @Test
// public void convertSwaggerToMarkdown() throws Exception {
// this.mockMvc.perform(get("/v2/api-docs")
// .accept(MediaType.APPLICATION_JSON))
// .andDo(Swagger2MarkupResultHandler.outputDirectory("docs/markdown/generated")
// .withMarkupLanguage(MarkupLanguage.MARKDOWN).build())
// .andExpect(status().isOk());
// }
// 一般寫測試兼輸出的方式
// @Test
// public void testIndex() throws Exception {
// this.mockMvc.perform(get("/api/test").accept(MediaType.APPLICATION_JSON))
// .andExpect(status().isOk())
// .andDo(document("test", preprocessResponse(prettyPrint())));
// }
class PsSwagger2MarkupHandler implements ResultHandler {
private String outputDir;
private final MarkupLanguage markupLanguage = MarkupLanguage.ASCIIDOC;
private String examplesFolderPath;
private final GroupBy pathsGroupedBy = GroupBy.AS_IS;
private final String encoding = "UTF-8";
private String passStr = "listRepositories,getCollectionResource,getItemResource,getCollectionResourceCompact,listSearches,errorHtml,listAllFormsOfMetadata,schema";
public PsSwagger2MarkupHandler(String outputDir) {
Validate.notEmpty(outputDir, "outputDir must not be empty!");
this.outputDir = outputDir;
}
@Override
public void handle(MvcResult result) throws Exception {
MockHttpServletResponse response = result.getResponse();
response.setCharacterEncoding(encoding);
String swaggerJson = response.getContentAsString();
// 去除掉 Spring Data Rest 提供的基礎路徑
List<String> passSummary = Arrays.asList(passStr.split(","));
ObjectMapper mapper = new ObjectMapper();
ObjectNode newPaths = mapper.createObjectNode();
JsonNode jsonRoot = mapper.readTree(swaggerJson);
JsonNode jsonPaths = jsonRoot.get("paths");
Iterator<String> keyit = jsonPaths.fieldNames();
while (keyit.hasNext()) {
String key = keyit.next();
JsonNode jsonPath = jsonPaths.get(key);
String summary = jsonPath.findPath("summary").asText();
if (!passSummary.contains(summary)) {
newPaths.set(key, jsonPath);
}
}
((ObjectNode) jsonRoot).set("paths", newPaths);
swaggerJson = jsonRoot.toString();
// 這邊輸出成 adoc
Swagger2MarkupConverter.fromString(swaggerJson).withMarkupLanguage(markupLanguage)
.withPathsGroupedBy(this.pathsGroupedBy)
.withExamples(examplesFolderPath).build().intoFolder(outputDir);
}
}
}
測試用的 H2 資料庫也需要改一下配置
test/resources/application.yml
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=-1
initialize: false
sql-script-encoding: UTF-8
jpa:
hibernate:
ddl-auto: create-drop
database-platform: H2
show-sql: true
generate-ddl: false
我們把 initialize 關掉 改用 jpa.hibernate.ddl-auto
因為測試的時候...springboottest 似乎不會自己引入 schema.sql import.sql 來執行
所以會造成啟動失敗 (沒有表格)
我們簡單一點讓 hibernete 根據 Entity 自訂產生對應的表格就可以了
因為在這邊我們沒有要真的驗證資料對不對, 只是需要讓他產生出 swagger 的 json 資料
如果你需要事先寫入資料的話 可以參考這做法 TestingDataSourceConfig.java
package com.example.book;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import javax.sql.DataSource;
import static org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType.H2;
@Configuration
public class TestingDataSourceConfig {
@Bean
@Primary
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("import.sql")
.build();
}
}
原則上來說, 這時候如果我們執行 gradle test, 就會在我們專案下產出類似這樣的資料
但是目前測試是錯誤的, 看起來像是套件間的問題
io.swagger.models.parameters.AbstractSerializableParameter.getDefaultValue()Ljava/lang/String;
java.lang.NoSuchMethodError: io.swagger.models.parameters.AbstractSerializableParameter.getDefaultValue()Ljava/lang/String;
at io.github.robwin.swagger2markup.utils.ParameterUtils.getDefaultValue(ParameterUtils.java:108)
at io.github.robwin.swagger2markup.builder.document.PathsDocument.parametersSection(PathsDocument.java:280)
at io.github.robwin.swagger2markup.builder.document.PathsDocument.path(PathsDocument.java:184)
at io.github.robwin.swagger2markup.builder.document.PathsDocument.createPathSections(PathsDocument.java:170)
at io.github.robwin.swagger2markup.builder.document.PathsDocument.paths(PathsDocument.java:142)
at io.github.robwin.swagger2markup.builder.document.PathsDocument.build(PathsDocument.java:127)
at io.github.robwin.swagger2markup.Swagger2MarkupConverter.buildDocuments(Swagger2MarkupConverter.java:119)
at io.github.robwin.swagger2markup.Swagger2MarkupConverter.intoFolder(Swagger2MarkupConverter.java:98)
at springfox.documentation.staticdocs.Swagger2MarkupResultHandler.handle(Swagger2MarkupResultHandler.java:74)
at org.springframework.test.web.servlet.MockMvc$1.andDo(MockMvc.java:177)
at com.example.book.Swagger2MarkupTest.convertSwaggerToAsciiDoc(Swagger2MarkupTest.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:147)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:129)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:46)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
1 test completed, 1 failed
我再找找如何修正吧...
比較詳細的說明可以先看我之前的版本
將 Swagger 輸出成 HTML 文檔
但是我舊的專案以前可以跑....打開來更新後就一樣套件衝突了....QQ