Gradle環(huán)境下導(dǎo)出Swagger為PDF

更多精彩博文政模,歡迎訪問(wèn)我的個(gè)人博客


說(shuō)明

我個(gè)人是一直使用Swagger作為接口文檔的說(shuō)明的岗宣。但是由于在一些情況下,接口文檔說(shuō)明需要以文件的形式交付出去淋样,如果再重新寫一份文檔難免有些麻煩耗式。于是在網(wǎng)上看到了Swagger2Markup + asciidoctor導(dǎo)出PDF的方法,百度一番后感覺(jué)網(wǎng)上的文章還是有很多沒(méi)有描述清楚的地方趁猴,遂還是硬著頭皮把官方的英文文檔大致瀏覽了一下刊咳,按照自己的思路整理出具體的步驟。

本文用到的工具:

  • Gradle - 4.10.3
  • SpringBoot - 2.1.6.RELEASE
  • Swagger - 2.9.2
  • Swagger2Markup - 1.3.3
  • asciidoctor
  • spring-restdocs-mockmvc

準(zhǔn)備Swagger數(shù)據(jù)

SpringBoot中使用Swagger的過(guò)程就不再贅述了儡司,下面是本文使用的范例:

@Configuration
@EnableSwagger2
class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.jptangchina.gradle.controller"))
            .paths(PathSelectors.any())
            .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
            .title("Swagger2Markup Test Api")
            .version("1.0")
            .build();
    }
}
@RestController
@RequestMapping("/user")
@Api(tags = "用戶接口")
public class UserController {

    @ApiOperation("用戶登錄")
    @ResponseBody
    @PostMapping("/login")
    public Result<Void> login(
        @ApiParam(value = "用戶名", example = "jptangchina", required = true) @RequestParam String username,
        @ApiParam(value = "密碼", example = "jptangchina", required = true) @RequestParam String password) {
        return Result.ok();
    }
}

使用org.asciidoctor.convert生成PDF(個(gè)人不推薦使用)

官方教程地址:https://github.com/Swagger2Markup/spring-swagger2markup-demo

僅為了簡(jiǎn)單的導(dǎo)出PDF而言娱挨,本文針對(duì)官方案例均有所改動(dòng),去掉了部分沒(méi)有用到的配置捕犬。

1. 獲取Swagger json文件

Swagger頁(yè)面本質(zhì)上也就是對(duì)json文件進(jìn)行解析跷坝。這里需要先編寫單元測(cè)試訪問(wèn)/v2/api-docs接口并將json文件保存到本地。

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
class SwaggerTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    public void generateAsciiDocsToFile() throws Exception {
        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);
        }
    }

}

System.getProperty("io.springfox.staticdocs.outputDir");來(lái)自于build.gradle中的配置

2. 將json文件轉(zhuǎn)換為adoc文件

轉(zhuǎn)換json文件需要使用到io.github.swagger2markup插件的convertSwagger2markup方法碉碉。

引入相關(guān)依賴:

buildscript {
    ...
    dependencies {
        ...
        classpath 'io.github.swagger2markup:swagger2markup-gradle-plugin:1.3.3'
    }
}
  
apply plugin: 'io.github.swagger2markup'

配置convertSwagger2markup:

ext {
    asciiDocOutputDir = file("${buildDir}/asciidoc")
    swaggerOutputDir = file("${buildDir}/swagger")
}

test {
    systemProperty 'io.springfox.staticdocs.outputDir', swaggerOutputDir
}

convertSwagger2markup {
    dependsOn test
    swaggerInput "${swaggerOutputDir}/swagger.json"
    outputDir asciiDocOutputDir
    config = [
            'swagger2markup.pathsGroupedBy' : 'TAGS',
    ]
}

更多config配置可以參考:http://swagger2markup.github.io/swagger2markup/1.3.3/#_swagger2markup_properties

3. 將adoc文件轉(zhuǎn)換為PDF文件

轉(zhuǎn)換PDF文件需要用到org.asciidoctor.convert插件的asciidoctor方法柴钻。
引入相關(guān)依賴:

buildscript {
    ...
    dependencies {
        ...
        classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.3'
    }
}
apply plugin: 'org.asciidoctor.convert'

手動(dòng)編寫index.adoc文件,放置到${asciiDocOutputDir.absolutePath}中:

include::{generated}/overview.adoc[]
include::{generated}/paths.adoc[]
include::{generated}/definitions.adoc[]
include::{generated}/security.adoc[]

{generated}默認(rèn)值為${build}/asciidoc垢粮,參見(jiàn):https://github.com/Swagger2Markup/swagger2markup-gradle-project-template

配置asciidoctor:

asciidoctor {
    dependsOn convertSwagger2markup
    // sourceDir中需要包含有之前手動(dòng)編寫的index.adoc文件
    sourceDir(asciiDocOutputDir.absolutePath)
    sources {
        include "index.adoc"
    }
    backends = ['pdf']
    attributes = [
            doctype: 'book',
            toc: 'left',
            toclevels: '3',
            numbered: '',
            sectlinks: '',
            sectanchors: '',
            hardbreaks: '',
            generated: asciiDocOutputDir
    ]
}

4. 編寫一個(gè)自定義task用來(lái)執(zhí)行上述流程:

task genPdf(type: Test, dependsOn: test) {
    include '**/*SwaggerTest.class'
    exclude '**/*'
    dependsOn(asciidoctor)
}

執(zhí)行g(shù)enPdf贴届,就可以生成Swagger對(duì)應(yīng)的PDF文件。

5. 小結(jié)

使用此方法步驟還是比較繁瑣的,總體來(lái)講就是json -> adoc -> pdf毫蚓,并且使用此種方法目前有幾個(gè)比較大的問(wèn)題我仍然沒(méi)有找到解決方案:

  • 從官方文檔中可以看到支持的語(yǔ)言默認(rèn)有EN, DE, FR, RU占键。沒(méi)錯(cuò),不支持CN元潘,從導(dǎo)出的文檔也可以看到畔乙,部分中文無(wú)法顯示,目前我也尚未找到是否有配置可以實(shí)現(xiàn)這個(gè)功能翩概。網(wǎng)上的文章部分是通過(guò)替換源代碼包里面的字體文件來(lái)實(shí)現(xiàn)啸澡,但是由于后面有更好的解決方案,這里就不再討論氮帐。
  • 從asciidoctorj-pdf的1.5.0-alpha.16版本以后(目前最新是1.5.0-alpha.18),這種方式生成文件會(huì)拋出異常洛姑,我個(gè)人并沒(méi)有深究這個(gè)異常上沐,有興趣的讀者可以通過(guò)修改版本號(hào)試一試。
  • 生成的adoc文件一般包含overview.adoc楞艾、paths.adoc参咙、definitions.adoc、security.adoc一共4個(gè)文件硫眯,這也是為什么要手動(dòng)編寫index.adoc進(jìn)行整合的原因蕴侧,感覺(jué)不太方便。
    綜上两入,我個(gè)人并不推薦采用此方式生成PDF净宵。

build.gradle完整文件參考:

buildscript {
    ext {
        springbootVersion = '2.1.6.RELEASE'
    }
    repositories {
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public'
        }
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springbootVersion}"
        classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.3'
        classpath 'io.github.swagger2markup:swagger2markup-gradle-plugin:1.3.3'
    }
}

repositories {
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public'
    }
}

apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'io.github.swagger2markup'
apply plugin: 'org.asciidoctor.convert'

group 'com.jptangchina'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8
targetCompatibility = 1.8

ext {
    asciiDocOutputDir = file("${buildDir}/asciidoc")
    swaggerOutputDir = file("${buildDir}/swagger")
    swaggerVersion = '2.9.2'
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile "io.springfox:springfox-swagger2:${swaggerVersion}"
    compile "io.springfox:springfox-swagger-ui:${swaggerVersion}"
    compile 'io.github.swagger2markup:swagger2markup:1.3.3'
    asciidoctor 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.16'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

test {
    systemProperty 'io.springfox.staticdocs.outputDir', swaggerOutputDir
}

convertSwagger2markup {
    dependsOn test
    swaggerInput "${swaggerOutputDir}/swagger.json"
    outputDir asciiDocOutputDir
    config = [
            'swagger2markup.pathsGroupedBy' : 'TAGS',
    ]
}

asciidoctor {
    dependsOn convertSwagger2markup
    sourceDir(asciiDocOutputDir.absolutePath)
    sources {
        include "index.adoc"
    }
    backends = ['pdf']
    attributes = [
            doctype: 'book',
            toc: 'left',
            toclevels: '3',
            numbered: '',
            sectlinks: '',
            sectanchors: '',
            hardbreaks: '',
            generated: asciiDocOutputDir
    ]
}

task genPdf(type: Test, dependsOn: test) {
    include '**/*SwaggerTest.class'
    exclude '**/*'
    dependsOn(asciidoctor)
}

使用asciidoctor-gradle-plugin生成PDF(推薦)

asciidoctor-gradle-plugin也是官方推薦的使用方式。相對(duì)前面的方式裹纳,使用起來(lái)更加簡(jiǎn)單择葡,也可以修改配置輸出中文。

1. 引入插件

plugins {
    id 'org.asciidoctor.jvm.pdf' version '2.2.0'
}

2. 編寫測(cè)試類生成adoc

與第一中方法不同的是剃氧,不需要再將json文件保存到本地了敏储。

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SwaggerTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    public void generateAsciiDocsToFile() throws Exception {
        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();

        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
            .withMarkupLanguage(MarkupLanguage.ASCIIDOC)
            .withOutputLanguage(Language.ZH)
            .withPathsGroupedBy(GroupBy.TAGS)
            .withGeneratedExamples()
            .withoutInlineSchema()
            .build();

        MockHttpServletResponse response = mvcResult.getResponse();
        String swaggerJson = response.getContentAsString();
        Swagger2MarkupConverter.from(swaggerJson)
            .withConfig(config)
            .build()
            .toFile(Paths.get(outputDir + "/swagger"));
    }
}

有興趣的讀者可以閱讀下toFile方法的源碼,里面對(duì)第一種方法生成的4個(gè)文件進(jìn)行了整合朋鞍,這也是不再需要手動(dòng)編寫index.adoc文件的原因已添。

3. 配置asciidoctorPdf


ext {
    asciiDocOutputDir = file("${buildDir}/asciidoc")
    // 創(chuàng)建字體與主題的文件夾
    pdfFontsDir = file("${buildDir}/fonts")
    pdfThemesDir = file("${buildDir}/themes")
    swaggerVersion = '2.9.2'
}

pdfThemes {
    local 'basic', {
        styleDir = pdfThemesDir
        // styleName會(huì)被程序用于匹配${styleName}-theme.yml,如default-styleName-theme.yml
        styleName = 'default'
    }
}
asciidoctorPdf{
    sourceDir(asciiDocOutputDir.absolutePath)
    sources {
        include "swagger.adoc"
    }
    fontsDir(pdfFontsDir.absolutePath)
    theme("basic")
}

本文字體與主題文件均來(lái)自于asciidoctorj-pdf-1.5.0-alpha.18.jar源碼包滥酥,其路徑位于:gems/asciidoctorj-pdf-1.5.0-alpha.18/data

4. 復(fù)制并修改default-theme.yml文件配置

為了解決中文無(wú)法顯示的問(wèn)題更舞,首先需要自行下載一個(gè)支持中文的字體文件。

修改主題文件恨狈,將mplus1p-regular-fallback.ttf替換為自己下載的字體文件的名稱疏哗。

M+ 1p Fallback:
  normal: your-font.ttf
  bold: your-font.ttf
  italic: your-font.ttf
  bold_italic: your-font.ttf

由于手動(dòng)指定了字體文件的路徑,所以除了中文以外,還需要將源碼中的其他字體文件一并復(fù)制到${pdfFontsDir}文件夾返奉。如果不愿意使用官方的字體贝搁,也可以考慮將default-theme.yml中其他的字體文件都修改為自己想要的文件。

保持task genPdf不變芽偏,再次運(yùn)行即可生成PDF文件雷逆,生成的文件默認(rèn)路徑為${build}/docs/asciidocPdf

小結(jié)

asciidoctor-gradle-plugin的方式可以支持配置字體與主題,通過(guò)配置不僅規(guī)避了中文無(wú)法顯示的問(wèn)題污尉,同時(shí)使用起來(lái)也更加簡(jiǎn)單膀哲。需要注意的是,采用此種方案生成出的文檔會(huì)在封面寫有項(xiàng)目的版本號(hào)被碗,此版本號(hào)為build.gradle中的version某宪,而非SwaggerConfig類中的version。

官方提供了很多配置锐朴,可以自行參考官方文檔查看兴喂。

build.gradle完整文件參考:

buildscript {
    ext {
        springbootVersion = '2.1.6.RELEASE'
    }
    repositories {
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public'
        }
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springbootVersion}"
    }
}

plugins {
    id 'org.asciidoctor.jvm.pdf' version '2.2.0'
}

repositories {
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public'
    }
}

apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group 'com.jptangchina'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8
targetCompatibility = 1.8

ext {
    asciiDocOutputDir = file("${buildDir}/asciidoc")
    pdfFontsDir = file("${buildDir}/fonts")
    pdfThemesDir = file("${buildDir}/themes")
    swaggerVersion = '2.9.2'
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile "io.springfox:springfox-swagger2:${swaggerVersion}"
    compile "io.springfox:springfox-swagger-ui:${swaggerVersion}"
    compile 'io.github.swagger2markup:swagger2markup:1.3.3'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

test {
    systemProperty 'io.springfox.staticdocs.outputDir', asciiDocOutputDir
}

pdfThemes {
    local 'basic', {
        styleDir = pdfThemesDir
        styleName = 'default'
    }
}
asciidoctorPdf{
    sourceDir(asciiDocOutputDir.absolutePath)
    sources {
        include "swagger.adoc"
    }
    fontsDir(pdfFontsDir.absolutePath)
    theme("basic")
}

task genPdf(type: Test, dependsOn: test) {
    include '**/*SwaggerTest.class'
    exclude '**/*'
    dependsOn(asciidoctorPdf)
}

參考

https://github.com/Swagger2Markup/swagger2markup
https://github.com/Swagger2Markup/spring-swagger2markup-demo
http://swagger2markup.github.io/swagger2markup/1.3.3


更多精彩博文,歡迎訪問(wèn)我的個(gè)人博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焚志,一起剝皮案震驚了整個(gè)濱河市衣迷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酱酬,老刑警劉巖壶谒,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異膳沽,居然都是意外死亡汗菜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門挑社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)呵俏,“玉大人,你說(shuō)我怎么就攤上這事滔灶∑账椋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵录平,是天一觀的道長(zhǎng)麻车。 經(jīng)常有香客問(wèn)我,道長(zhǎng)斗这,這世上最難降的妖魔是什么动猬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮表箭,結(jié)果婚禮上赁咙,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好彼水,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布崔拥。 她就那樣靜靜地躺著,像睡著了一般凤覆。 火紅的嫁衣襯著肌膚如雪链瓦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天盯桦,我揣著相機(jī)與錄音慈俯,去河邊找鬼。 笑死拥峦,一個(gè)胖子當(dāng)著我的面吹牛贴膘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播略号,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼步鉴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了璃哟?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喊递,失蹤者是張志新(化名)和其女友劉穎随闪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骚勘,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡材失,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年持钉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡请垛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出要门,到底是詐尸還是另有隱情钦铁,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布殉疼,位于F島的核電站梯浪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瓢娜。R本人自食惡果不足惜挂洛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眠砾。 院中可真熱鬧虏劲,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吻育,卻和暖如春念秧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背布疼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工摊趾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人游两。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓砾层,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贱案。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肛炮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容