首先我们看一看效果图,高大上有木有啊有木有。
主要参考
https://leongfeng.github.io/2017/02/20/springboot-springfox-swagger2markup-spring-restdoc/
https://github.com/Swagger2Markup/spring-swagger2markup-demo
http://swagger2markup.github.io/swagger2markup/1.3.1/
这里笔者主要是参照官网的spring-swagger2markup-demo进行参考搭建的。
接下来我们来说一下是如何搞定的
1.在项目增加一个文件夹docs
直接从spring-swagger2markup-demo里把这个文件夹复制到自己的项目里就可以,复制的时候还有个manual_content1.adoc 和 manual_content2.adoc的文件,这个文件是接口文档的自定义说明部分,我用不到所以就删了,文件删除之后还要把index.adoc里对应的地方也删掉。
index.adoc 内容如下:
include::{generated}/overview.adoc[]
include::{generated}/paths.adoc[]
include::{generated}/security.adoc[]
include::{generated}/definitions.adoc[]
2.项目中添加maven依赖和插件
properties部分
UTF-8
UTF-8
1.8
1.5.8.RELEASE
19.0
2.5.0
4.2.1
2.0
3.10-beta2
0.1.46
3.3.2
1.8.3
1.4.1
1.2.0
dependency部分
io.swagger
swagger-annotations
1.5.6
com.google.guava
guava
${com.google.guava.version}
com.fasterxml.jackson.dataformat
jackson-dataformat-smile
com.fasterxml.jackson.module
jackson-module-afterburner
io.springfox
springfox-swagger2
${springfox.version}
test
io.springfox
springfox-swagger-ui
${springfox.version}
io.springfox
springfox-bean-validators
${springfox.version}
io.springfox
springfox-staticdocs
${springfox.version}
org.springframework.restdocs
spring-restdocs-mockmvc
com.fasterxml.jackson.module
jackson-module-jsonSchema
test
io.github.robwin
assertj-swagger
0.2.0
test
io.github.swagger2markup
swagger2markup-spring-restdocs-ext
${swagger2markup.version}
test
org.slf4j
slf4j-api
org.slf4j
jcl-over-slf4j
runtime
org.springframework.boot
spring-boot-configuration-processor
true
maven plugin部分
org.springframework.boot
spring-boot-maven-plugin
com.spotify
docker-maven-plugin
0.4.13
true
10.0.0.95:5000/JAVA_Account-Service:${project.version}
${project.basedir}/src/main/docker
/
${project.build.directory}
${project.build.finalName}.jar
org.apache.maven.plugins
maven-surefire-plugin
${swagger.output.dir}
${swagger.snippetOutput.dir}
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}
true
test
convertSwagger2markup
org.asciidoctor
asciidoctor-maven-plugin
1.5.3
org.asciidoctor
asciidoctorj-pdf
1.5.0-alpha.10.1
org.jruby
jruby-complete
1.7.21
${asciidoctor.input.directory}
index.adoc
book
left
3
${generated.asciidoc.directory}
output-html
test
process-asciidoc
html5
${asciidoctor.html.output.directory}
output-pdf
test
process-asciidoc
pdf
${asciidoctor.pdf.output.directory}
org.apache.maven.plugins
maven-jar-plugin
2.4
true
lib/
io.github.robwin.swagger2markup.petstore.Application
maven-dependency-plugin
package
copy-dependencies
${project.build.directory}/lib
maven-resources-plugin
2.7
copy-resources
prepare-package
copy-resources
${project.build.outputDirectory}/static/docs
${asciidoctor.html.output.directory}
${asciidoctor.pdf.output.directory}
这里需要做如下几点说明
- log4j2的部分,官方的demo中使用的logback,但是我的项目里用的是log4j2,所以这里我有做处理,slf4j换成了log4j2,撰文时,官方的demo里用springfox.version是2.4.0,笔者全部升级为了2.5.0
3.swagger的使用部分
SwaggerConfig代码
package com.weds.account;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static springfox.documentation.builders.PathSelectors.ant;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Arrays;
import static java.util.Arrays.asList;
import static springfox.documentation.builders.PathSelectors.ant;
@EnableSwagger2
@Configuration
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {
@Bean
public Docket restApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xx.account.web")).paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("账户微服务接口文档")
.description("账户微服务接口文档")
.contact(new Contact("XXX数据", "http://www.weds.com.cn", "[email protected]"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("1.0.0")
.build();
}
}
注意:如果你是参照官方文档添加的maven依赖,那这个SwaggerConfig 必须是在单元测试的包模块下,因为官方的依赖里springfox-bean-validators的scope是test,只有在test下才会被成功引入到SwaggerConfig 中。restApi部分采用的是basePackage方式,这样可以忽略比如actuator的rest接口也被拿来生成了接口文档。
3.REST接口以及实体类定义部分
package com.weds.account.web.inter;
import com.weds.framework.core.common.model.PagedResponse;
import com.weds.account.entity.req.*;
import com.weds.account.entity.resp.App_Account_Query_Resp;
import com.weds.framework.core.common.model.JsonResult;
import org.springframework.web.bind.annotation.*;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
/**
* 应用账户相关的接口
* Created by Caoheyang on 2018-01-06.
*/
@RestController
@RequestMapping(value = "/app/account", produces = {APPLICATION_JSON_VALUE})
public interface AppAccountInter {
/**
* 新增应用账户接口
* Created by Caoheyang on 2018-01-06.
*
* @param req
* @return
*/
@PutMapping(path = "/add")
public JsonResult add(@RequestBody App_Account_Add_Req req);
/**
* 修改应用账户信息接口
* Created by Caoheyang on 2018-01-06.
*
* @param req
* @return
*/
@PostMapping(path = "/modify")
public JsonResult modify(@RequestBody App_Account_Modify_Req req);
/**
* 修改应用账户密码接口
* Created by Caoheyang on 2018-01-06.
*
* @param req
* @return
*/
@PostMapping(path = "/modify_pass")
public JsonResult modifyPass(@RequestBody App_Account_Modify_Pass_Req req);
/**
* 删除应用账户接口
* Created by Caoheyang on 2018-01-06.
*
* @param app_account 应用账户
* @return
*/
@DeleteMapping(path = "/delete/{app_account}")
public JsonResult delete(@PathVariable("app_account") String app_account);
/**
* 查询某个应用账户接口
* Created by Caoheyang on 2018-01-06.
*
* @return
*/
@PostMapping(path = "/searchone")
public JsonResult searchOne();
/**
* 查询应用账户列表接口
* Created by Caoheyang on 2018-01-06.
*
* @return
*/
@PostMapping(path = "/search")
public JsonResult> search();
/**
* 应用账户登陆接口
* Created by Caoheyang on 2018-01-06.
*
* @param req
* @return
*/
@PostMapping(path = "/login")
public JsonResult login(@RequestBody App_Account_Login_Req req);
}
package com.weds.account.web;
import com.weds.account.entity.req.*;
import com.weds.account.entity.resp.App_Account_Query_Resp;
import com.weds.account.web.inter.AppAccountInter;
import com.weds.framework.core.common.model.JsonResult;
import com.weds.framework.core.common.model.PagedResponse;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;
/**
* 应用账户相关服务的Controller Created by Caoheyang on 2018-01-06.
*/
@RestController
@Api(value = "应用账户管理", description = "应用账户相关的接口")
public class AppAccountController implements AppAccountInter {
@ApiOperation(value = "新增应用账户接口", notes = "新增应用账户接口")
@Override
public JsonResult add(
@ApiParam(value = "入参", required = true) @RequestBody App_Account_Add_Req req) {
return null;
}
@ApiOperation(value = "修改应用账户信息接口", notes = "修改应用账户信息接口")
@Override
public JsonResult modify(
@ApiParam(value = "入参", required = true) @RequestBody App_Account_Modify_Req req) {
return null;
}
@ApiOperation(value = "修改应用账户密码接口", notes = "修改应用账户密码接口")
@Override
public JsonResult modifyPass(
@ApiParam(value = "入参", required = true) @RequestBody App_Account_Modify_Pass_Req req) {
return null;
}
@ApiOperation(value = "删除应用账户接口", notes = "删除应用账户接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "app_account", value = "应用账户", required = true, paramType = "path", dataType = "string")})
@Override
public JsonResult delete(@PathVariable("app_account") String app_account) {
return null;
}
@ApiOperation(value = "查询某个应用账户接口", notes = "查询某个应用账户接口")
@Override
public JsonResult searchOne() {
return null;
}
@ApiOperation(value = "查询应用账户列表接口", notes = "查询应用账户列表接口")
@Override
public JsonResult> search() {
return null;
}
@ApiOperation(value = "应用账户登陆接口", notes = "应用账户登陆接口")
@Override
public JsonResult login(
@ApiParam(value = "入参", required = true) @RequestBody App_Account_Login_Req req) {
return null;
}
}
package com.weds.account.entity.req;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Map;
/**
* 新增应用账户接口 入参
* Created by Caoheyang on 2018-01-08.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class App_Account_Add_Req implements Serializable{
//应用账户,用一个应用下的应用账户具有唯一性
@ApiModelProperty(required = true, dataType = "string", example = "admin",
value = "应用账户,用一个应用下的应用账户具有唯一性")
@Getter
@Setter
private String app_account;
//应用账户标签,json格式,根据平台入驻时与账户微服务预定义的标签,由平台应用自行存储
@ApiModelProperty(required = true, dataType = "string", example = "{\"class\":\"03\",\"type\":\"teacher\"}",
value = "应用账户标签,json格式,根据平台入驻时与账户微服务预定义的标签,由平台应用自行存储")
@Getter
@Setter
private Map label;
//应用账户密码 DES加密之后的密文
@ApiModelProperty(required = true, dataType = "string", example = "FFWERWRFGP34DFWE",
value = "应用账户密码,DES加密之后的密文")
@Getter
@Setter
private String password;
//角色编码,以”;角色码1;角色码2;”形式存储
@ApiModelProperty(required = false, dataType = "string", example = ";0011;0032;",
value = "角色编码,以”;角色码1;角色码2;”形式存储")
@Getter
@Setter
private String role_nos;
//添加人 默认值为system
@ApiModelProperty(required = false, dataType = "string", example = "system",
value = "添加人 默认值为system")
@Getter
@Setter
private String inserted_by;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注1",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_1;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注2",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_2;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注3",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_3;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注4",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_4;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注5",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_5;
}
package com.weds.account.entity.resp;
import com.weds.account.entity.App_Account_Role;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.management.relation.Role;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 查询某个应用账户接口 返回数据结构
* Created by Caoheyang on 2018-01-09.
*/
public class App_Account_Query_Resp implements Serializable {
//基础账户编号
@ApiModelProperty(required = true, dataType = "int", example = "123",
value = "基础账户编号")
@Getter
@Setter
private int basic_acc_no;
//应用账户
@ApiModelProperty(required = true, dataType = "string", example = "admin",
value = "应用账户")
@Getter
@Setter
private String app_account;
//身份证号码
@ApiModelProperty(required = false, dataType = "string", example = "370685199010111215",
value = "身份证号码")
@Getter
@Setter
private String id_card;
//真实姓名
@ApiModelProperty(required = true, dataType = "string", example = "张三",
value = "真实姓名")
@Getter
@Setter
private String name;
//性别 男 女
@ApiModelProperty(required = true, dataType = "int", example = "男",
value = "性别")
@Getter
@Setter
private String sex;
//手机号码1
@ApiModelProperty(required = false, dataType = "string", example = "18888888888",
value = "手机号码1")
@Getter
@Setter
private String mobilephone;
//手机号码2
@ApiModelProperty(required = false, dataType = "string", example = "18888888888",
value = "手机号码2")
@Getter
@Setter
private String mobilephone2;
//固话
@ApiModelProperty(required = false, dataType = "string", example = "0535-80888888",
value = "固话")
@Getter
@Setter
private String telephone;
//邮箱1
@ApiModelProperty(required = false, dataType = "string", example = "[email protected]",
value = "邮箱1")
@Getter
@Setter
private String email;
//邮箱2
@ApiModelProperty(required = false, dataType = "string", example = "[email protected]",
value = "邮箱2")
@Getter
@Setter
private String email2;
//应用账户状态
@ApiModelProperty(required = true, dataType = "int", example = "0",
value = "应用账户状态")
@Getter
@Setter
private int app_account_status;
//标签 JSON数据体
@ApiModelProperty(required = true, dataType = "string", example = "{\"class\":\"03\",\"type\":\"teacher\"}",
value = "应用账户标签,json格式,根据平台入驻时与账户微服务预定义的标签,由平台应用自行存储")
@Getter
@Setter
private Map label;
//角色,信息 JONS 数组
@ApiModelProperty(required = true, dataType = "string", example = "admin",
value = "角色数组")
@Getter
@Setter
private List role_nos;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注1",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_1;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注2",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_2;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注3",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_3;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注4",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_4;
//备用标注,应用自定义存储,自行解析
@ApiModelProperty(required = false, dataType = "string", example = "备注5",
value = "备用标注,应用自定义存储,自行解析")
@Getter
@Setter
private String column_5;
}
注意:
1.我这里用了lombok所以没有get,set方法,只有 @Getter和@Setter注解
2.在与swagger2markup的集成中发现,必须用@ApiParam注解,而且实体上不可以加ApiModel否则swagger2markup部分的生成会失败,无法映射接口的入参和返回值部分。
3.在swagger2中response部分不需要写,关于泛型部分也会自己直接映射的。
- @ApiImplicitParam在 @RequestBody联合使用的过程中,在swaggerui下没问题,但是在swagger2markup下是有问题的,无法生成swagger2markup文档,需要注意!
4.使用swagger2markup生成文档
1.需要新建一个Swagger2MarkupTest类,代码如下
package com.weds.account;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.test.context.junit4.SpringJUnit4ClassRunner;
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.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@AutoConfigureRestDocs(outputDir = "build/asciidoc/snippets")
@SpringBootTest(classes = {AccountServiceApplication.class, SwaggerConfig.class})
@AutoConfigureMockMvc
public class Swagger2MarkupTest {
@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);
}
}
}
2.执行maven 的clean命令 然后执行test命令
插件会自动生成接口文件到target下,文件是adoc格式的。
由于插件会提现把接口文档生成为adoc文件,放到target下,所以对项目的实际运行速度毫无影响。
这时如果执行spring boot的run 命令,请求http://localhost:1234/docs/index.html发现是500的错误,但是如果把执行maven的package命令,然后在执行spring boot的run 命令或者 java -jar 项目.jar 把项目跑起来,然后在访问 http://localhost:1234/docs/index.html 就ok了,并且 http://localhost:1234/swagger-ui.html 也是ok的。
github开源代码:https://github.com/caoheyang/swagger2markupdemo
oschina开源代码:https://gitee.com/caoheyang/swagger2markupdemo
请注意代码中还有很多的依赖是自己封装的,请参照博客查找swagger2markup部分即可。