背景
项目需要暴露API给其他项目团队调用,为方便接口联调,需提供API文档给服务调用方使用。
系统环境
开发语言: Java
JDK:1.8
SpringBoot:latest
springfox:2.9.2
操作步骤
1.集成swagger2、swagger2-ui
step1: 添加相关依赖jar包
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
step2: 项目代码增加swagger2相关配置,具体参考如下:
package com.xxx.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.annotations.ApiIgnore;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author XXX
* @project XXX
* @package com.xxx.xxx.config
* @date 2020/10/19 16:31
*/
@Configuration
@EnableSwagger2
@ComponentScan("com.xxx.xxx.controller")
public class Swagger2Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xxx.xxx"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Demo Swagger2 RESTFul APIs")
.description("api root:http://xxx.com.cn/api/xxx/v1")
.termsOfServiceUrl("http://xxx.com.cn/")
.contact("xxx@xxx.com")
.version("1.0")
.build();
}
}
step3: 修改controller代码,示例如下:
package com.xxx.xxx.controller;
import com.alibaba.fastjson.JSONObject;
import com.xxx.xxx.common.RetMessage;
import com.xxx.xxx.pojo.*;
import com.xxx.xxx.service.MobileXxxAPIService;
import com.xxxx.xxx.utils.CollectionUtils;
import io.swagger.annotations.*;
import io.swagger.annotations.Example;
import io.swagger.annotations.ExampleProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author xxx
* @project demo
* @package com.xxx.xxx.controller
* @description xxx数据API接口
* @date 2020/9/16 16:10
*/
@Controller
@RequestMapping("/mobile/xxx")
@Api(value = "xxx 移动端api", description = "xxx 移动端api")
public class MobileXxxAPIController {
private final Logger logger = LoggerFactory.getLogger ( getClass ( ) );
@Autowired
private MobileXxxAPIService mobileXxxAPIService;
/**
* 当前账户余额
* @param data
* @return
*/
@ApiOperation(value = "当前账户余额", notes = "获取当前账户余额", httpMethod = "POST")
@ApiResponses({
@ApiResponse(code = 201, message = "请求已经被实现"),
@ApiResponse(code = 400, message = "请求参数有误"),
@ApiResponse(code = 401, message = "用户验证失败"),
@ApiResponse(code = 403, message = "服务器禁止访问"),
@ApiResponse(code = 404, message = "请求资源未找到"),
@ApiResponse(code = 500, message = "服务器内部解析出错")})
@RequestMapping(value = "/accountBalance")
@ResponseBody
public Object getAccountBalance(@RequestBody AccountBalanceRequestBody data) {
return CollectionUtils.createMap (
RetMessage.DATA, mobileXxxAPIService.getAccountBalance (data.getOrg_id()),
RetMessage.META, CollectionUtils.createMap (
RetMessage.MSG, RetMessage.MSGSUCCESS, RetMessage.CODE, RetMessage.CODESUCESS
) );
}
}
2.安装插件生成离线html、PDF文档
step1:修改pom.xml,主要增加的配置项如下:
<properties>
<swagger2markup.version>1.2.0</swagger2markup.version>
<asciidoctor.input.directory>${project.basedir}/src/docs/asciidoc</asciidoctor.input.directory>
<swagger.output.dir>${project.build.directory}/swagger</swagger.output.dir>
<swagger.snippetOutput.dir>${project.build.directory}/asciidoc/snippets</swagger.snippetOutput.dir>
<generated.asciidoc.directory>${project.build.directory}/asciidoc/generated</generated.asciidoc.directory>
<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
<swagger.input>${swagger.output.dir}/swagger.json</swagger.input>
</properties>
<dependencies>
<!--offline doc-->
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<!--<scope>test</scope>-->
</dependency>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-spring-restdocs-ext</artifactId>
<version>${swagger2markup.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-staticdocs</artifactId>
<version>2.6.1</version>
<!--<scope>test</scope>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<pluginRepositories>
<pluginRepository>
<id>jcenter-snapshots</id>
<name>jcenter</name>
<url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
</pluginRepository>
<pluginRepository>
<id>jcenter-releases</id>
<name>jcenter</name>
<url>http://jcenter.bintray.com</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<io.springfox.staticdocs.outputDir>${swagger.output.dir}</io.springfox.staticdocs.outputDir>
<io.springfox.staticdocs.snippetsOutputDir>${swagger.snippetOutput.dir}</io.springfox.staticdocs.snippetsOutputDir>
</systemPropertyVariables>
</configuration>
</plugin>
<!-- 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>true</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.6</version>
<!-- Include Asciidoctor PDF for pdf generation -->
<dependencies>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj-pdf</artifactId>
<version>1.5.0-alpha-zh.16</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
<version>1.7.21</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>
<attributes>
<pdf-style>cn</pdf-style>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
<!-- specify the main class for the manifest -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<!--important!!! specify the main class for the manifest!!!-->
<!--important!!! specify the main class for the manifest!!!-->
<!--important!!! specify the main class for the manifest!!!-->
<mainClass>com.mskj.dop.ApplicationMain</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- copy dependencies to the lib directory -->
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- copy the generated documents -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static/docs</outputDirectory>
<resources>
<resource>
<directory>${asciidoctor.html.output.directory}</directory>
</resource>
<resource>
<directory>${asciidoctor.pdf.output.directory}</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
step2: 新增测试代码(生成html、PDF文件是在test阶段触发的)
具体的测试代码,参考如下:
package com.xxx.xxx;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
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.restdocs.operation.preprocess.Preprocessors;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author xxx
* @project xxx
* @package com.xxx.xxx
* @date 2020/10/21 14:03
*/
@WebAppConfiguration
@RunWith(SpringRunner.class)
@AutoConfigureRestDocs(outputDir = "build/asciidoc/snippets")
@SpringBootTest
@AutoConfigureMockMvc
public class Swagger2MarkupTest {
@Autowired
private MockMvc mockMvc;
@Test
public void createSpringfoxSwaggerJson() 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);
}
}
}
step3: 项目源码目录下增加index.adoc文件
index.adoc文件内容:
include::{generated}/overview.adoc[]
include::{generated}/paths.adoc[]
include::{generated}/security.adoc[]
include::{generated}/definitions.adoc[]
step4: 使用IDE mvn clean test触发文档构建
step5:查看PDF或者HTML文档,具体文档路径:
3.解决PDF中文乱码问题
具体参考【6】中的两种方式,笔者选择方式1
⚠️下载asciidoctorj-pdf-1.5.0-alpha-zh.16.jar,请从【5】中提到的GitHub项目中下载
参考资料
[1].https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
[2].https://www.javazhiyin.com/57982.html
[3].https://github.com/houko/SpringBootUnity
[4].https://www.jianshu.com/p/033d650164c4
[5].https://blog.csdn.net/han_chuang/article/details/98748944
[6].https://www.hotbak.net/key/Swagger%E4%BD%BF%E7%94%A8%E4%B8%89%E8%A7%A3%E5%86%B3swagger2markup%E7%94%9F%E6%88%90%E7%9A%84%E7%A6%BB%E7%BA%BFpdfCSDN%E5%8D%9A%E5%AE%A2.html