1 介绍

官网:https://swagger.io/image-20210901160434736.png Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。功能主要包含以下几点:
A. 使得前后端分离开发更加方便,有利于团队协作
B. 接口文档在线自动生成,降低后端开发人员编写接口文档的负担
C. 接口功能测试
使用Swagger只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。
直接使用Swagger, 需要按照Swagger的规范定义接口, 实际上就是编写Json文件,编写起来比较繁琐、并不方便, 。而在项目中使用,我们一般会选择一些现成的框架来简化文档的编写,而这些框架是基于Swagger的,如knife4j。knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。而我们要使用kinfe4j,需要在pom.xml中引入如下依赖即可:

  1. <dependency>
  2. <groupId>com.github.xiaoymin</groupId>
  3. <artifactId>knife4j-spring-boot-starter</artifactId>
  4. <version>3.0.2</version>
  5. </dependency>

2 使用方式

接下来,我们就将我们的项目集成Knife4j,来自动生成接口文档。这里我们还是需要再创建一个新的分支v1.2,在该分支中进行knife4j的集成,集成测试完毕之后,没有问题,我们再将v1.2分支合并到master。
1). 导入knife4j的maven坐标

  1. <dependency>
  2. <groupId>com.github.xiaoymin</groupId>
  3. <artifactId>knife4j-spring-boot-starter</artifactId>
  4. <version>3.0.2</version>
  5. </dependency>

2). 导入knife4j相关配置类
这里我们就不需要再创建一个新的配置类了,我们直接在WebMvcConfig配置类中声明即可。
A. 在该配置类中加上两个注解 @EnableSwagger2 @EnableKnife4j ,开启Swagger和Knife4j的功能。
B. 在配置类中声明一个Docket类型的bean, 通过该bean来指定生成文档的信息。

  1. @Slf4j
  2. @Configuration
  3. @EnableSwagger2
  4. @EnableKnife4j
  5. public class WebMvcConfig extends WebMvcConfigurationSupport {
  6. /**
  7. * 设置静态资源映射
  8. * @param registry
  9. */
  10. @Override
  11. protected void addResourceHandlers(ResourceHandlerRegistry registry) {
  12. log.info("开始进行静态资源映射...");
  13. registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
  14. registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
  15. }
  16. /**
  17. * 扩展mvc框架的消息转换器
  18. * @param converters
  19. */
  20. @Override
  21. protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
  22. log.info("扩展消息转换器...");
  23. //创建消息转换器对象
  24. MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
  25. //设置对象转换器,底层使用Jackson将Java对象转为json
  26. messageConverter.setObjectMapper(new JacksonObjectMapper());
  27. //将上面的消息转换器对象追加到mvc框架的转换器集合中
  28. converters.add(0,messageConverter);
  29. }
  30. @Bean
  31. public Docket createRestApi() {
  32. // 文档类型
  33. return new Docket(DocumentationType.SWAGGER_2)
  34. .apiInfo(apiInfo())
  35. .select()
  36. .apis(RequestHandlerSelectors.basePackage("com.itheima.reggie.controller"))
  37. .paths(PathSelectors.any())
  38. .build();
  39. }
  40. private ApiInfo apiInfo() {
  41. return new ApiInfoBuilder()
  42. .title("瑞吉外卖")
  43. .version("1.0")
  44. .description("瑞吉外卖接口文档")
  45. .build();
  46. }
  47. }

注意: Docket声明时,指定的有一个包扫描的路径,该路径指定的是Controller所在包的路径。因为Swagger在生成接口文档时,就是根据这里指定的包路径,自动的扫描该包下的@Controller, @RestController, @RequestMapping等SpringMVC的注解,依据这些注解来生成对应的接口文档。
3). 设置静态资源映射
由于Swagger生成的在线文档中,涉及到很多静态资源,这些静态资源需要添加静态资源映射,否则接口文档页面无法访问。因此需要在 WebMvcConfig类中的addResourceHandlers方法中增加如下配置。

  1. registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
  2. registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

4). 在LoginCheckFilter中设置不需要处理的请求路径
需要将Swagger及Knife4j相关的静态资源直接放行,无需登录即可访问,否则我们就需要登录之后,才可以访问接口文档的页面。
在原有的不需要处理的请求路径中,再增加如下链接:

  1. "/doc.html",
  2. "/webjars/**",
  3. "/swagger-resources",
  4. "/v2/api-docs"

image-20210901171132242.png

3 查看接口文档

经过上面的集成配置之后,我们的项目集成Swagger及Knife4j就已经完成了,接下来我们可以重新启动项目,访问接口文档,访问链接为: http://localhost:8080/doc.htmlimage-20210901200739975.png我们可以看到,在所有的Controller中提供的所有的业务增删改查的接口,全部都已经自动生成了,我们通过接口文档可以看到请求的url、请求方式、请求参数、请求实例、响应的参数,响应的示例。 并且呢,我们也可以通过这份在线的接口文档,对接口进行测试。image-20210901201229838.png
注意: 由于我们服务端的Controller中的业务增删改查的方法,都是必须登录之后才可以访问的,所以,我们在测试时候,也是需要先访问登录接口。登录完成之后,我们可以再访问其他接口进行测试。
我们不仅可以在浏览器浏览生成的接口文档,Knife4j还支持离线文档,对接口文档进行下载,支持下载的格式有:markdown、html、word、openApi。image-20210901214706928.png

4 常用注解

4.1 问题说明

在上面我们直接访问Knife4j的接口文档页面,可以查看到所有的接口文档信息,但是我们发现,这些接口文档分类及接口描述都是Controller的类名(驼峰命名转换而来)及方法名,而且在接口文档中,所有的请求参数,响应数据,都没有中文的描述,并不知道里面参数的含义,接口文档的可读性很差。image-20210901215244539.png

4.2 注解介绍

为了解决上述的问题,Swagger提供了很多的注解,通过这些注解,我们可以更好更清晰的描述我们的接口,包含接口的请求参数、响应数据、数据模型等。核心的注解,主要包含以下几个:

注解 位置 说明
@Api 加载Controller类上,表示对类的说明
@ApiModel 类(通常是实体类) 描述实体类的作用
@ApiModelProperty 属性 描述实体类的属性
@ApiOperation 方法 说明方法的用途、作用
@ApiImplicitParams 方法 表示一组参数说明
@ApiImplicitParam 方法 用在@ApiImplicitParams注解中,指定一个请求参数的各个方面的属性

4.3 注解测试

1). 实体类

可以通过 @ApiModel , @ApiModelProperty 来描述实体类及属性

  1. @Data
  2. @ApiModel("套餐")
  3. public class Setmeal implements Serializable {
  4. private static final long serialVersionUID = 1L;
  5. @ApiModelProperty("主键")
  6. private Long id;
  7. //分类id
  8. @ApiModelProperty("分类id")
  9. private Long categoryId;
  10. //套餐名称
  11. @ApiModelProperty("套餐名称")
  12. private String name;
  13. //套餐价格
  14. @ApiModelProperty("套餐价格")
  15. private BigDecimal price;
  16. //状态 0:停用 1:启用
  17. @ApiModelProperty("状态")
  18. private Integer status;
  19. //编码
  20. @ApiModelProperty("套餐编号")
  21. private String code;
  22. //描述信息
  23. @ApiModelProperty("描述信息")
  24. private String description;
  25. //图片
  26. @ApiModelProperty("图片")
  27. private String image;
  28. @TableField(fill = FieldFill.INSERT)
  29. private LocalDateTime createTime;
  30. @TableField(fill = FieldFill.INSERT_UPDATE)
  31. private LocalDateTime updateTime;
  32. @TableField(fill = FieldFill.INSERT)
  33. private Long createUser;
  34. @TableField(fill = FieldFill.INSERT_UPDATE)
  35. private Long updateUser;
  36. }

2). 响应实体R

  1. @Data
  2. @ApiModel("返回结果")
  3. public class R<T> implements Serializable{
  4. @ApiModelProperty("编码")
  5. private Integer code; //编码:1成功,0和其它数字为失败
  6. @ApiModelProperty("错误信息")
  7. private String msg; //错误信息
  8. @ApiModelProperty("数据")
  9. private T data; //数据
  10. @ApiModelProperty("动态数据")
  11. private Map map = new HashMap(); //动态数据
  12. //省略静态方法 ....

3). Controller类及其中的方法**

描述Controller、方法及其方法参数,可以通过注解: @Api, @APIOperation, @ApiImplicitParams, @ApiImplicitParam

  1. @RestController
  2. @RequestMapping("/setmeal")
  3. @Slf4j
  4. @Api(tags = "套餐相关接口")
  5. public class SetmealController {
  6. @Autowired
  7. private SetmealService setmealService;
  8. @Autowired
  9. private CategoryService categoryService;
  10. @Autowired
  11. private SetmealDishService setmealDishService;
  12. /**
  13. * 新增套餐
  14. * @param setmealDto
  15. * @return
  16. */
  17. @PostMapping
  18. @CacheEvict(value = "setmealCache",allEntries = true)
  19. @ApiOperation(value = "新增套餐接口")
  20. public R<String> save(@RequestBody SetmealDto setmealDto){
  21. log.info("套餐信息:{}",setmealDto);
  22. setmealService.saveWithDish(setmealDto);
  23. return R.success("新增套餐成功");
  24. }
  25. /**
  26. * 套餐分页查询
  27. * @param page
  28. * @param pageSize
  29. * @param name
  30. * @return
  31. */
  32. @GetMapping("/page")
  33. @ApiOperation(value = "套餐分页查询接口")
  34. @ApiImplicitParams({
  35. @ApiImplicitParam(name = "page",value = "页码",required = true),
  36. @ApiImplicitParam(name = "pageSize",value = "每页记录数",required = true),
  37. @ApiImplicitParam(name = "name",value = "套餐名称",required = false)
  38. })
  39. public R<Page> page(int page,int pageSize,String name){
  40. //分页构造器对象
  41. Page<Setmeal> pageInfo = new Page<>(page,pageSize);
  42. Page<SetmealDto> dtoPage = new Page<>();
  43. LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
  44. //添加查询条件,根据name进行like模糊查询
  45. queryWrapper.like(name != null,Setmeal::getName,name);
  46. //添加排序条件,根据更新时间降序排列
  47. queryWrapper.orderByDesc(Setmeal::getUpdateTime);
  48. setmealService.page(pageInfo,queryWrapper);
  49. //对象拷贝
  50. BeanUtils.copyProperties(pageInfo,dtoPage,"records");
  51. List<Setmeal> records = pageInfo.getRecords();
  52. List<SetmealDto> list = records.stream().map((item) -> {
  53. SetmealDto setmealDto = new SetmealDto();
  54. //对象拷贝
  55. BeanUtils.copyProperties(item,setmealDto);
  56. //分类id
  57. Long categoryId = item.getCategoryId();
  58. //根据分类id查询分类对象
  59. Category category = categoryService.getById(categoryId);
  60. if(category != null){
  61. //分类名称
  62. String categoryName = category.getName();
  63. setmealDto.setCategoryName(categoryName);
  64. }
  65. return setmealDto;
  66. }).collect(Collectors.toList());
  67. dtoPage.setRecords(list);
  68. return R.success(dtoPage);
  69. }
  70. /**
  71. * 删除套餐
  72. * @param ids
  73. * @return
  74. */
  75. @DeleteMapping
  76. @CacheEvict(value = "setmealCache",allEntries = true)
  77. @ApiOperation(value = "套餐删除接口")
  78. public R<String> delete(@RequestParam List<Long> ids){
  79. log.info("ids:{}",ids);
  80. setmealService.removeWithDish(ids);
  81. return R.success("套餐数据删除成功");
  82. }
  83. /**
  84. * 根据条件查询套餐数据
  85. * @param setmeal
  86. * @return
  87. */
  88. @GetMapping("/list")
  89. @Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
  90. @ApiOperation(value = "套餐条件查询接口")
  91. public R<List<Setmeal>> list(Setmeal setmeal){
  92. LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
  93. queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
  94. queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
  95. queryWrapper.orderByDesc(Setmeal::getUpdateTime);
  96. List<Setmeal> list = setmealService.list(queryWrapper);
  97. return R.success(list);
  98. }
  99. }

4). 重启服务测试
我们上述通过Swagger的注解,对实体类及实体类中的属性,以及Controller和Controller的方法进行描述,接下来,我们重新启动服务,然后看一下自动生成的接口文档有何变化。
image-20210901221213897.png在接口文档的页面中,我们可以看到接口的中文描述,清晰的看到每一个接口是做什么的,接口方法参数什么含义,参数是否是必填的,响应结果的参数是什么含义等,都可以清楚的描述出来。
总之,我们要想清晰的描述一个接口,就需要借助于Swagger给我们提供的注解。