后端 API 接口文档 Swagger 使用指南
前言
作为一个以前后端分离为模式开发小组,我们每隔一段时间都进行这样一个场景:前端人员和后端开发在一起热烈的讨论"哎,你这参数又变了啊","接口怎么又请求不通了啊","你再试试,我打个断点调试一下.."。可以看到在前后端沟通中出现了不少问题。
对于这样的问题,之前一直没有很好的解决方案,直到它的出现,没错...这就是我们今天要讨论的神器:swagger,一款致力于解决接口规范化、标准化、文档化的开源库,一款真正的开发神器。
一:swagger是什么?
Swagger是一款RESTFUL接口的文档在线自动生成+功能测试功能软件。Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务。目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法,参数和模型紧密集成到服务器。
这个解释简单点来讲就是说,swagger是一款可以根据resutful风格生成的生成的接口开发文档,并且支持做测试的一款中间软件。
二:为什么要使用swaager?
2.1:对于后端开发人员来说
- 不用再手写WiKi接口拼大量的参数,避免手写错误
- 对代码侵入性低,采用全注解的方式,开发简单
- 方法参数名修改、增加、减少参数都可以直接生效,不用手动维护
- 缺点:增加了开发成本,写接口还得再写一套参数配置
2.2:对于前端开发来说
- 后端只需要定义好接口,会自动生成文档,接口功能、参数一目了然
- 联调方便,如果出问题,直接测试接口,实时检查参数和返回值,就可以快速定位是前端还是后端的问题
2.3:对于测试
- 对于某些没有前端界面UI的功能,可以用它来测试接口
- 操作简单,不用了解具体代码就可以操作
- 操作简单,不用了解具体代码就可以操作
三:如何搭一个swagger
3.1:引入swagger的依赖
目前推荐使用2.7.0版本,因为2.6.0版本有bug,而其他版本又没有经过验证
一:引入Swagger依赖库<!--引入swagger--><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version></dependency>
3.2:springBoot整合swagger
@Configuration@EnableSwagger2public class SwaggerConfig { @Bean public Docket productApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //添加ApiOperiation注解的被扫描 .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder().title(”swagger和springBoot整合“).description(”swagger的API文档") .version("1.0").build(); }}
3.3:swagger的注解
swagger的核心在于注解,接下来就着重讲一下swagger的注解:
四:在项目中集成swagger
4.1:在controller中使用注解
package com.youjia.swagger.controller;import com.youjia.swagger.constants.CommonConstants;import com.youjia.swagger.model.Film;import com.youjia.swagger.model.ResultModel;import com.youjia.swagger.service.FilmService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;import io.swagger.annotations.ApiResponse;import io.swagger.annotations.ApiResponses;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.CollectionUtils;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.List;import java.util.Objects;/** * @Auther: wyq * @Date: 2018/12/29 14:50 */@RestController@Api(value = "电影Controller", tags = { "电影访问接口" })@RequestMapping("/film")public class FilmController { @Autowired private FilmService filmService; /** * 添加一个电影数据 * * @param * @return */ @ApiOperation(value = "添加一部电影") @PostMapping("/addFilm") @ApiResponses(value = { @ApiResponse(code = 1000, message = "成功"), @ApiResponse(code = 1001, message = "失败"), @ApiResponse(code = 1002, response = Film.class,message = "缺少参数") }) public ResultModel addFilm(@ApiParam("电影名称") @RequestParam("filmName") String filmName, @ApiParam(value = "分数", allowEmptyValue = true) @RequestParam("score") Short score, @ApiParam("发布时间") @RequestParam(value = "publishTime",required = false) String publishTime, @ApiParam("创建者id") @RequestParam("creatorId") Long creatorId) { if (Objects.isNull(filmName) || Objects.isNull(score) || Objects.isNull(publishTime) || StringUtils .isEmpty(creatorId)) { return new ResultModel(ResultModel.failed, "参数错误"); } Film filmPOM = new Film(); filmPOM.setFilmName(filmName); filmPOM.setScore(score); DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date publishTimeDate = null; try { publishTimeDate = simpleDateFormat.parse(publishTime); } catch (Exception ex) { ex.printStackTrace(); } filmPOM.setPublishTime(publishTimeDate); filmPOM.setCreatorId(creatorId); Boolean result = filmService.addFilm(filmPOM); if (result) { return new ResultModel(CommonConstants.SUCCESSMSG); } return new ResultModel(CommonConstants.FAILD_MSG); } /** * 根据电影名字获取电影 * * @param fileName * @return */ @GetMapping("/getFilms") @ApiOperation(value = "根据名字获取电影") @ApiResponses(value = { @ApiResponse(code = 1000, message = "成功"), @ApiResponse(code = 1001, message = "失败"), @ApiResponse(code = 1002, message = "缺少参数") }) public ResultModel getFilmsByName(@ApiParam("电影名称") @RequestParam("fileName") String fileName) { if (StringUtils.isEmpty(fileName)) { return CommonConstants.getErrorResultModel(); } List<Film> films = filmService.getFilmByName(fileName); if (!CollectionUtils.isEmpty(films)) { return new ResultModel(films); } return CommonConstants.getErrorResultModel(); } /** * 根据电影名更新 * * @return */ @PostMapping("/updateScore") @ApiOperation(value = "根据电影名修改分数") @ApiResponses(value = { @ApiResponse(code = 1000, message = "成功"), @ApiResponse(code = 1001, message = "失败"), @ApiResponse(code = 1002, message = "缺少参数") }) public ResultModel updateFilmScore(@ApiParam("电影名称") @RequestParam("fileName") String fileName, @ApiParam("分数") @RequestParam("score") Short score) { if (StringUtils.isEmpty(fileName) || Objects.isNull(score)) { return CommonConstants.getErrorResultModel(); } filmService.updateScoreByName(fileName, score); return CommonConstants.getSuccessResultModel(); } /** * 根据电影名删除电影 * * @param request * @return */ @PostMapping("/delFilm") @ApiOperation(value = "根据电影名删除电影") @ApiImplicitParams({ @ApiImplicitParam(name = "filmName", value = "电影名", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "id", value = "电影id", dataType = "int", paramType = "query") }) public ResultModel deleteFilmByNameOrId(HttpServletRequest request) { //电影名 String filmName = request.getParameter("filmName"); //电影id Long filmId = Long.parseLong(request.getParameter("id")); filmService.deleteFilmOrId(filmName,filmId); return CommonConstants.getSuccessResultModel(); } /** * 根据id获取电影 * * @param id * @return */ @PostMapping("/{id}") @ApiOperation("根据id获取电影") @ApiImplicitParam(name = "id", value = "电影id", dataType = "long", paramType = "path", required = true) public ResultModel getFilmById(@PathVariable Long id) { if (Objects.isNull(id)) { return CommonConstants.getLessParamResultModel(); } Film film = filmService.getFilmById(id); if (Objects.nonNull(film)) { return new ResultModel(film); } return CommonConstants.getErrorResultModel(); } /** * 修改整个电影 * * @param film * @return */ @PostMapping("/insertFilm") @ApiOperation("插入一部电影") public ResultModel insertFilm(@ApiParam("电影实体对象") @RequestBody Film film) { if (Objects.isNull(film)) { return CommonConstants.getLessParamResultModel(); } Boolean isSuccess = filmService.insertFilm(film); if (isSuccess) { return CommonConstants.getSuccessResultModel(); } return CommonConstants.getErrorResultModel(); }}
4.2:访问本地链接
http://localhost:8080/swagger-ui.html#/
可以看出访问的url都很清晰的展示在它最终的页面上,我们打开一个方法:可以看出方法的请求参数清晰的的罗列出来,包括方法的返回值。并且有一个很重要的功能,只需要点下方的try it out就可以进行接口测试,
五:使用swagger需要注意的问题
- 对于只有一个HttpServletRequest参数的方法,如果参数小于5个,推荐使用 @ApiImplicitParams的方式单独封装每一个参数;如果参数大于5个,采用定义一个对象去封装所有参数的属性,然后使用@APiParam的方式
- 默认的访问地址:ip:port/swagger-ui.html#/,但是在shiro中,会拦截所有的请求,必须加上默认访问路径(比如项目中,就是ip:port/context/swagger-ui.html#/),然后登陆后才可以看到
- 在GET请求中,参数在Body体里面,不能使用@RequestBody。在POST请求,可以使用@RequestBody和@RequestParam,如果使用@RequestBody,对于参数转化的配置必须统一
- controller必须指定请求类型,否则swagger会把所有的类型(6种)都生成出来
- swagger在生产环境不能对外暴露,可以使用@Profile({“dev”, “prod”,“pre”})指定可以使用的环境
六:总结
swagger作为一款辅助性的工具,能大大提升我们的和前端的沟通效率,接口是一个非常重要的传递数据的媒介,每个接口的签名、方法参数都非常重要。一个良好的文档非常重要,如果采用手写的方式非常容易拼写错误,而swagger可以自动化生成参数文档,这一切都加快了我们的沟通效率。并且可以替代postman的作用。实在是开发编程必备良品啊。