1、Swagger简介html
上一篇文章中咱们介绍了Spring Boot对Restful的支持,这篇文章咱们继续讨论这个话题,不过,咱们这里再也不讨论Restful API如何实现,而是讨论Restful API文档的维护问题。前端
在平常的工做中,咱们每每须要给前端(WEB端、IOS、Android)或者第三方提供接口,这个时候咱们就须要给他们提供一份详细的API说明文档。但维护一份详细的文档可不是一件简单的事情。首先,编写一份详细的文档自己就是一件很费时费力的事情,另外一方面,因为代码和文档是分离的,因此很容易致使文档和代码的不一致。这篇文章咱们就来分享一种API文档维护的方式,即经过Swagger来自动生成Restuful API文档。java
那什么是Swagger?咱们能够直接看下官方的描述:web
THE WORLD'S MOST POPULAR API TOOLING Swagger is the world’s largest framework of API developer tools for the OpenAPI Specification(OAS), enabling development across the entire API lifecycle, from design and documentation, to test and deployment.
这段话首先告诉你们Swagger是世界上最流行的API工具,而且Swagger的目的是支撑整个API生命周期的开发,包括设计、文档以及测试和部署。这篇文章中咱们会用到Swagger的文档管理和测试功能。spring
对Swagger的做用有了基本的认识后,咱们如今来看看怎么使用。json
2、Swagger与Spring boot集成api
第一步:引入对应jar包:app
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.0</version> </dependency>
第二步,基本信息配置:工具
@Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.pandy.blog.rest")) .paths(PathSelectors.regex("/rest/.*")) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Blog系统Restful API") .description("Blog系统Restful API") .termsOfServiceUrl("http://127.0.0.1:8080/") .contact("liuxiaopeng") .version("1.0") .build(); } }
基础的配置是对整个API文档的描述以及一些全局性的配置,对全部接口起做用。这里涉及到两个注解:post
@Configuration是表示这是一个配置类,是JDK自带的注解,前面的文章中也已作过说明。
@EnableSwagger2的做用是启用Swagger2相关功能。
在这个配置类里面我么实例化了一个Docket对象,这个对象主要包括三个方面的信息:
(1)整个API的描述信息,即ApiInfo对象包括的信息,这部分信息会在页面上展现。
(2)指定生成API文档的包名。
(3)指定生成API的路径。按路径生成API可支持四种模式,这个能够参考其源码:
public class PathSelectors { private PathSelectors() { throw new UnsupportedOperationException(); } public static Predicate<String> any() { return Predicates.alwaysTrue(); } public static Predicate<String> none() { return Predicates.alwaysFalse(); } public static Predicate<String> regex(final String pathRegex) { return new Predicate<String>() { public boolean apply(String input) { return input.matches(pathRegex); } }; } public static Predicate<String> ant(final String antPattern) { return new Predicate<String>() { public boolean apply(String input) { AntPathMatcher matcher = new AntPathMatcher(); return matcher.match(antPattern, input); } }; } }
从源码能够看出,Swagger总共支持任何路径都生成、任何路径都不生成以及正则匹配和ant 模式匹配四种方式。你们可能比较熟悉的是前三种,最后一种ant匹配,若是不熟悉ant的话就直接忽略吧,前三种应该足够你们在平常工做中使用了。
有了上面的配置咱们就能够看到效果了,我在com.pandy.blog.rest这个包下面有一个ArticleRestController这个类,源码以下:
@RestController public class ArticleRestController { @Autowired private ArticleService articleService; @RequestMapping(value = "/rest/article", method = POST, produces = "application/json") public WebResponse<Map<String, Object>> saveArticle(@RequestBody Article article) { article.setUserId(1L); articleService.saveArticle(article); Map<String, Object> ret = new HashMap<>(); ret.put("id", article.getId()); WebResponse<Map<String, Object>> response = WebResponse.getSuccessResponse(ret); return response; } @RequestMapping(value = "/rest/article/{id}", method = DELETE, produces = "application/json") public WebResponse<?> deleteArticle(@PathVariable Long id) { Article article = articleService.getById(id); article.setStatus(-1); articleService.updateArticle(article); WebResponse<Object> response = WebResponse.getSuccessResponse(null); return response; } @RequestMapping(value = "/rest/article/{id}", method = PUT, produces = "application/json") public WebResponse<Object> updateArticle(@PathVariable Long id, @RequestBody Article article) { article.setId(id); articleService.updateArticle(article); WebResponse<Object> response = WebResponse.getSuccessResponse(null); return response; } @RequestMapping(value = "/rest/article/{id}", method = GET, produces = "application/json") public WebResponse<Article> getArticle(@PathVariable Long id) { Article article = articleService.getById(id); WebResponse<Article> response = WebResponse.getSuccessResponse(article); return response; } @RequestMapping(value = "/test/{id}", method = GET, produces = "application/json") public WebResponse<?> getNoApi(){ WebResponse<?> response = WebResponse.getSuccessResponse(null); return response; } }
启动Spring boot,而后访问:http://127.0.0.1:8080/swagger-ui.html便可看到以下结果:
这个页面上能够看到,除了最后一个接口/test/{id}外,其余接口都生成对应的文档,最后一个接口由于不知足咱们配置的路径——“/rest/.*”,因此没有生成文档。
咱们还能够点进去看一下每个具体的接口,咱们这里以“POST /rest/article”这个接口为例:
能够看到,Swagger为每个接口都生成了返回结果和请求参数的示例,而且能直接经过下面的"try it out"进行接口访问,方面你们对接口进行测试。总体上感受Swagger仍是很强大的,配置也比较简单。
3、Swagger API详细配置
不过你们看到这里确定会有点疑问:
第一个问题:这个返回结果和请求参数都没有文字性的描述,这个可不能够配置?
第二个问题:这个请求参应该是直接根据对象反射出来的结果,可是不是对象的每一个属性都是必传的,另外参数的值也不必定知足咱们的需求,这个可否配置?
答案确定是能够的,如今咱们就来解决这两个问题,直接看配置的代码:
package com.pandy.blog.rest; import com.pandy.blog.dto.WebResponse; import com.pandy.blog.po.Article; import com.pandy.blog.service.ArticleService; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.springframework.web.bind.annotation.RequestMethod.DELETE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.PUT; @RestController @RequestMapping("/rest") public class ArticleRestController { @Autowired private ArticleService articleService; @RequestMapping(value = "/article", method = POST, produces = "application/json") @ApiOperation(value = "添加文章", notes = "添加新的文章", tags = "Article",httpMethod = "POST") @ApiImplicitParams({ @ApiImplicitParam(name = "title", value = "文章标题", required = true, dataType = "String"), @ApiImplicitParam(name = "summary", value = "文章摘要", required = true, dataType = "String"), @ApiImplicitParam(name = "status", value = "发布状态", required = true, dataType = "Integer") }) @ApiResponses({ @ApiResponse(code=200,message="成功",response=WebResponse.class), }) public WebResponse<Map<String,Object>> saveArticle(@RequestBody Article article){ articleService.saveArticle(article); Map<String,Object> ret = new HashMap<>(); ret.put("id",article.getId()); WebResponse<Map<String,Object>> response = WebResponse.getSuccessResponse(ret); return response; } @ApiOperation(value = "删除文章", notes = "根据ID删除文章", tags = "Article",httpMethod = "DELETE") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "文章ID", required = true, dataType = "Long") }) @RequestMapping(value = "/{id}",method = DELETE,produces = "application/json") public WebResponse<?> deleteArticle(@PathVariable Long id){ Article article = articleService.getById(id); article.setStatus(-1); articleService.saveArticle(article); return WebResponse.getSuccessResponse(new HashMap<>()); } @ApiOperation(value = "获取文章列表", notes = "能够根据标题进行模糊查询", tags = "Article",httpMethod = "GET") @ApiImplicitParams({ @ApiImplicitParam(name = "title", value = "文章标题", required = false, dataType = "String"), @ApiImplicitParam(name = "pageSize", value = "每页文章数量", required = false, dataType = "Integer"), @ApiImplicitParam(name = "pageNum", value = "分页的页码", required = false, dataType = "Integer") }) @RequestMapping(value = "/article/list", method = GET, produces = "application/json") public WebResponse<?> listArticles(String title, Integer pageSize, Integer pageNum) { if (pageSize == null) { pageSize = 10; } if (pageNum == null) { pageNum = 1; } int offset = (pageNum - 1) * pageSize; List<Article> articles = articleService.getArticles(title, 1L, offset, pageSize); return WebResponse.getSuccessResponse(articles); } @ApiOperation(value = "更新文章", notes = "更新文章内容", tags = "Article",httpMethod = "PUT") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "文章ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "title", value = "文章标题", required = false, dataType = "String"), @ApiImplicitParam(name = "summary", value = "文章摘要", required = false, dataType = "String"), @ApiImplicitParam(name = "status", value = "发布状态", required = false, dataType = "Integer") }) @RequestMapping(value = "/article/{id}", method = PUT, produces = "application/json") public WebResponse<?> updateArticle(@PathVariable Long id,@RequestBody Article article){ article.setId(id); articleService.updateArticle(article); return WebResponse.getSuccessResponse(new HashMap<>()); } }
咱们解释一下代码中几个注解及相关属性的具体做用:
@ApiOperation,整个接口属性配置:
value:接口说明,展现在接口列表。
notes:接口详细说明,展现在接口的详情页。
tags:接口的标签,相同标签的接口会在一个标签页下展现。
httpMethod:支持的HTTP的方法。
@ApiImplicitParams,@ApiImplicitParam的容器,可包含多个@ApiImplicitParam注解
@ApiImplicitParam,请求参数属性配置:
name:参数名称
value:参数说明
required:是否必须
dataType:数据类型
@ApiResponses,@ApiResponse容器,能够包含多个@ApiResponse注解
@ApiResponse,返回结果属性配置:
code:返回结果的编码。
message:返回结果的说明。
response:返回结果对应的类。
完成以上配置后,咱们再看下页面效果:
列表页:
能够看到,如今接口都位于Article这个tag下,而且接口后面也有了咱们配置好的说明。咱们再看下”POST /rest/article“这个接口的详情页:
图片太大,只截取了title属性的展现,其余几个参数的相似。咱们能够从页面上看到请求参数的说明是有的,不过这不是咱们预期的效果,若是咱们的参数仅仅是简单类型,这种方式应该没问题,但如今的问题是咱们的请求参数是一个对象,那如何配置呢?这就涉及到另外两个注解:@ApiModel和@ApiModelProperty,咱们仍是先看代码,而后再解释,这样更容易理解:
@ApiModel(value="article对象",description="新增&更新文章对象说明") public class Article { @Id @GeneratedValue @ApiModelProperty(name = "id",value = "文章ID",required = false,example = "1") private Long id; @ApiModelProperty(name = "title",value = "文章标题",required = true,example = "测试文章标题") private String title; @ApiModelProperty(name = "summary",value = "文章摘要",required = true,example = "测试文章摘要") private String summary; @ApiModelProperty(hidden = true) private Date createTime; @ApiModelProperty(hidden = true) private Date publicTime; @ApiModelProperty(hidden = true) private Date updateTime; @ApiModelProperty(hidden = true) private Long userId; @ApiModelProperty(name = "status",value = "文章发布状态",required = true,example = "1") private Integer status; @ApiModelProperty(name = "type",value = "文章分类",required = true,example = "1") private Integer type; }
@ApiModel是对整个类的属性的配置:
value:类的说明
description:详细描述
@ApiModelProperty是对具体每一个字段的属性配置:
name:字段名称
value:字段的说明
required:是否必须
example:示例值
hidden:是否显示
完成上面的配置后,咱们再来看效果:
如今咱们能够看到,字段的说明都已经展现出来,而且,示例中字段的值也变成了咱们配置的example属性对应的值了。这样,一份完整的API文档就生成了,而且该文档与代码紧密的联系在一块儿,而不是隔离的两个部分。除此以外,咱们还能够直接经过该文档很方便的进行测试,咱们只须要点击Example Value下黄色的框,里面的内容就会自动复制到article对应的value框中,而后在点击“Try it out”就能够发起http请求了。
点击Try it out后,咱们就能够看到返回的结果:
操做仍是很方便的,相比Junit和postman,经过Swagger来测试会更加便捷,固然,Swagger的测试并不能代替单元测试,不过,在联调的时候仍是有很是大的做用的。
4、总结
整体上来讲,Swagger的配置仍是比较简单的,而且Swagger可以自动帮咱们生成文档确实为咱们节省了很多工做,对后续的维护也提供了很大的帮助。除此以外,Swagger还能根据配置自动为咱们生成测试的数据,而且提供对应的HTTP方法,这对咱们的自测和联调工做也有很多的帮助,因此我仍是推荐你们在平常的开发中去使用Swagger,应该能够帮助你们在必定程度上提升工做效率的。最后,留一个问题给你们思考吧,就是该文档是能够直接经过页面来访问的,那咱们总不能把接口直接暴露在生产环境吧,尤为是要对外提供服务的系统,那咱们怎么才能在生产环节中关闭这个功能呢?方法有不少,你们能够本身尝试一下。