• -------------------------------------------------------------
  • ====================================

API文档生成之Springfox-Swagger框架的简单使用

建站指南 dewbay 4年前 (2020-03-18) 2110次浏览 已收录 0个评论 扫描二维码

后端开发一定厌倦了和前端对API接口的麻烦,之前还是小白时愣是手写 word 文档给前端工程师使用,各种 copy 字段加解释…

然而,有更好用的API框架可以使用,可以让我们摆脱这种烦恼,下面说下 spring-fox swagger 的简单使用
注:swagger 是一种API规范,springfox 是其规范的一种实现

  1. 引入 swagger 依赖
  1. <dependency>
  2. <groupId>io.springfox</groupId>
  3. <artifactId>springfox-swagger2</artifactId>
  4. <version>2.8.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>io.springfox</groupId>
  8. <artifactId>springfox-swagger-ui</artifactId>
  9. <version>2.8.0</version>
  10. </dependency>
  1. 配置 swagger,本例中使用的是 springboot 配置,如需 xml 配置请自行百度
    编写SwaggerConfig
    添加注解
    @Configuration
    @EnableSwagger2
    Docket.apis()中配置的包名为要扫描生成API的 Controller 包名,将会为该包下的 controller 生成相关文档
    ApiInfo 为相关信息,该信息将会展示在文档中
  1. /**
  2. * Created by wangqichang on 2018/6/22.
  3. */
  4. @Configuration
  5. @EnableSwagger2
  6. public class SwaggerConfig{
  7. @Bean
  8. public Docket createRestApi(){
  9. return new Docket(DocumentationType.SWAGGER_2)
  10. .apiInfo(apiInfo())
  11. .select()
  12. .apis(RequestHandlerSelectors.basePackage(“com.kichun.controller”))
  13. .paths(PathSelectors.any())
  14. .build();
  15. }
  16. private ApiInfo apiInfo(){
  17. return new ApiInfoBuilder()
  18. .title(“炒鸡好用的系統API)
  19. .description(“更多内容请咨询开发者 王启昌”)
  20. .termsOfServiceUrl(http://kichun.xin/&#8221;)
  21. .contact(“wangqichang”)
  22. .version(“1.0”)
  23. .build();
  24. }
  25. }
  1. 在需要添加的 controller 类和方法中加上 api 注解
    加在类上
    @Api(value = “字典操作接口”,tags = {“字典常量操作”})
    加在方法上
    @ApiOperation(value = “删除字典类型”,notes = “”)
    @ApiImplicitParam(name = “id”,value = “字典 ID”,required = true,dataType = “String”)
  1. /**
  2. * <p>
  3. * 前端控制器
  4. * </p>
  5. *
  6. * @author wangqichang
  7. * @since 2018-06-15
  8. */
  9. @Api(value = “字典操作接口”,tags = {“字典常量操作”})
  10. @RestController
  11. @RequestMapping(“/dataDict”)
  12. public class DataDictController{
  13. @Autowired
  14. private DataDictService dataDictService;
  15. /**
  16. * 删除字典类型
  17. *
  18. * @author Qichang.Wang
  19. * @date 16:04 2018/6/15
  20. */
  21. @ApiOperation(value = “删除字典类型”,notes = “”)
  22. @ApiImplicitParam(name = “id”,value = “字典 ID”,required = true,dataType = “String”)
  23. @RequestMapping(value = “/delete”, method = RequestMethod.POST)
  24. public ResultDTO delete(String id) {
  25. if (StrUtil.isBlank(id)) {
  26. return new ResultDTO(400, “参数异常”);
  27. }
  28. BaseDictEnum baseDictEnum = BaseDictEnum.fromId(id);
  29. if (baseDictEnum != null) {
  30. return new ResultDTO(500, “暂不支持删除顶级字典!”);
  31. }
  32. boolean insert = dataDictService.deleteById(id);
  33. return insert ? new ResultDTO(200, “success”) : new ResultDTO(500, “fail”);
  34. }

当有多个参数时用@ApiImplicitParams

  1. /**
  2. * 登录
  3. *
  4. * @author Qichang.Wang
  5. * @date 17:27 2018/6/14
  6. */
  7. @ApiOperation(value = “用户登录接口”, notes = “”)
  8. @ApiImplicitParams(value = { @ApiImplicitParam(name = “name”, value = “用户名”, required = true),
  9. @ApiImplicitParam(name = “pwd”, value = “密码”, required = true),
  10. @ApiImplicitParam(name = “rememberMe”, value = “勾选时传递 true”, required = false) })
  11. @RequestMapping(value = “/login”, method = RequestMethod.POST)
  12. public ResultDTO login(String name, String pwd, String rememberMe){
  13. if (StrUtil.isBlank(name) || StrUtil.isBlank(pwd)) {
  14. return new ResultDTO(500, “用户名及角色不能为空”);
  15. }
  16. Boolean remember = false;
  17. if (StrUtil.isNotBlank(rememberMe) || “true”.equals(rememberMe.trim())) {
  18. remember = true;
  19. }
  20. UsernamePasswordToken token = new UsernamePasswordToken(name, pwd, remember);
  21. try {
  22. SecurityUtils.getSubject().login(token);
  23. } catch (Exception e) {
  24. log.error(“登录失败,信息:{}”, e.getMessage());
  25. return new ResultDTO(401, “登录失败, 用户名或密码不匹配”);
  26. }
  27. return new ResultDTO(200, “登录成功!”);
  28. }

当参数是个对象时,参数前加入
@RequestBody @ModelAttribute
这样才能将对象的属性给到 swagger

  1. * 新增更新字典类型
  2. *
  3. * @author Qichang.Wang
  4. * @date 16:05 2018/6/15
  5. */
  6. @ApiOperation(value = “新增及更新字典”,notes = “对象包含 ID 时为更新,不包含 ID 时为新增”)
  7. @ApiImplicitParam(name = “dict”,value = “字典对象,详细请查看对象属性”,required = true,dataType = “DataDict”)
  8. @RequestMapping(value = “/update”, method = RequestMethod.POST)
  9. public ResultDTO update(@RequestBody @ModelAttribute DataDict dict){
  10. if (StrUtil.isBlank(dict.getParentId()) || StrUtil.isBlank(dict.getName())) {
  11. return new ResultDTO(400, “参数异常”);
  12. }
  13. BaseDictEnum baseDictEnum = BaseDictEnum.fromId(dict.getId());
  14. if (baseDictEnum != null) {
  15. return new ResultDTO(500, “暂不支持修改顶级字典!”);
  16. }
  17. if (StrUtil.isBlank(dict.getId())){
  18. DataDict exist = new DataDict();
  19. exist.setName(dict.getName());
  20. exist.setParentId(dict.getParentId());
  21. List<DataDict> dataDicts = dataDictService.selectList(new EntityWrapper<>(dict));
  22. if (CollUtil.isNotEmpty(dataDicts)) {
  23. return new ResultDTO(500, “该类型名已存在”);
  24. }
  25. dataDictService.insert(dict);
  26. } else {
  27. boolean insert = dataDictService.updateById(dict);
  28. }
  29. return new ResultDTO(200,“操作成功”);
  30. }

注解的各个属性相信各位大佬一看就能明了,不多加解释了
加好注解后,重启应用
接下来是见证奇迹的时候
访问路径:应用地址+端口+swagger-ui.html 例如http://localhost:8800/swagger-ui.html

API文档生成之Springfox-Swagger框架的简单使用
image.png

展开 contoller 效果

API文档生成之Springfox-Swagger框架的简单使用
image.png

展开接口效果

API文档生成之Springfox-Swagger框架的简单使用
image.png

点 try it out 可以直接对接口进行测试,彻底抛弃什么 Postman,RestfulClient 等调试工具!

只要注释写的全,再复杂的应用统统搞定有没有!!!
改了接口自动重新生成API文档有没有!!!
会写代码的前端工程师都能看懂有没有!!!
还是看不懂的前端直接打屎算了有没有!!!

美中不足是字段详细我还不确定能不能加注释,你们研究下吧,我觉得达到这效果已经 OK 了

20180921 补充:
针对接口请求对象及返回对象使用上述方式无法展示字段说明。使用@ModelAttribute 注解可能会导致一些莫名错误无法获取参数

实体注解

@ApiModel 用于实体类上,标注为需要生成文档的实体
@ApiModelProperty(value = “url 菜单”,required = true) 用于实体属性,标注该属性的说明,require 为 false 时不会在页面上进行展示该属性

  1. import com.baomidou.mybatisplus.enums.IdType;
  2. import io.swagger.annotations.ApiModel;
  3. import io.swagger.annotations.ApiModelProperty;
  4. /**
  5. * <p>
  6. * 权限表
  7. * </p>
  8. *
  9. * @author wangqichang
  10. * @since 2018-06-14
  11. */
  12. @Data
  13. @Accessors(chain = true)
  14. @TableName(“t_resource”)
  15. @ApiModel
  16. public class Resource extends Model<Resource> {
  17. private static final long serialVersionUID = 1L;
  18. /**
  19. * 资源 ID
  20. */
  21. @TableId(type = IdType.UUID)
  22. @ApiModelProperty(hidden = true)
  23. private String id;
  24. /**
  25. * url
  26. */
  27. @ApiModelProperty(value = “url 菜单”,required = true)
  28. private String url;
  29. /**
  30. * 父 ID
  31. */
  32. @TableField(“parent_id”)
  33. @ApiModelProperty(hidden = true)
  34. private String parentId;
  35. /**
  36. * 资源名
  37. */
  38. @TableField(“resource_name”)
  39. @ApiModelProperty(value = “资源名”,required = true)
  40. private String resourceName;
  41. /**
  42. * 资源类型(menu,btn)
  43. */
  44. @TableField(“resource_type”)
  45. @ApiModelProperty(value = “资源类型(menu,btn)”,required = true)
  46. private String resourceType;
  47. /**
  48. * 资源码
  49. */
  50. @TableField(“resource_code”)
  51. @ApiModelProperty(value = “资源码”,required = true)
  52. private String resourceCode;
  53. /**
  54. * 资源描述
  55. */
  56. @TableField(“resource_desc”)
  57. @ApiModelProperty(value = “资源描述”,required = true)
  58. private String resourceDesc;
  59. /**
  60. * 菜单图标
  61. */
  62. @ApiModelProperty(value = “菜单图标”,required = true)
  63. private String myicon;
  64. /**
  65. * 是否可用
  66. */
  67. @ApiModelProperty(hidden = true)
  68. private Integer available;
  69. /**
  70. * 创建时间
  71. */
  72. @TableField(“create_time”)
  73. @ApiModelProperty(hidden = true)
  74. private Date createTime;
  75. /**
  76. * 创建人 id
  77. */
  78. @TableField(“create_id”)
  79. @ApiModelProperty(hidden = true)
  80. private Date createId;
  81. /**
  82. * 排序
  83. */
  84. @ApiModelProperty(value = “资源排序”,required = true)
  85. private Integer sort;
  86. /**
  87. * 临时字段,供前端选中使用
  88. */
  89. @TableField(exist = false)
  90. @ApiModelProperty(hidden = true)
  91. private Boolean isChecked;
  92. @Override
  93. protected Serializable pkVal() {
  94. return this.id;
  95. }
  96. }

在实体添加注解后,在接口中使用如下注解

@ApiParam(name = “资源对象”,value = “json 格式”,required = true)
标识该对象为文档实体

  1. @ApiOperation(value = “权限资源新增及更新”, notes = “有 ID 更新,没 ID 为新增”)
  2. @RequestMapping(value = “/resource”, method = RequestMethod.POST)
  3. public ResultDTO resource(@RequestBody @ApiParam(name = “资源对象”,value = “json 格式”,required = true) Resource resource) {
  4. Integer count = 0;
  5. try {
  6. count = userService.updateResources(resource);
  7. } catch (Exception e) {
  8. log.error(“新增或更新资源异常:{}”, e.getMessage());
  9. return new ResultDTO(500, e.getMessage());
  10. }
  11. return count > 0 ? new ResultDTO(200, “保存成功”) : new ResultDTO(500, “操作失败,请查询日志”);
  12. }

实体注释文档界面

需要点击 model 才会显示说明

API文档生成之Springfox-Swagger框架的简单使用
image.png

效果如下

API文档生成之Springfox-Swagger框架的简单使用
image.png

关于 shiro 权限拦截

使用中发现开启 shiro 后无法访问 swagger api 界面
问题原因:swagger 内置接口被权限拦截导致无法访问
处理方式:
浏览器打开 F12 调试,选择 network,查看 status 为非 200 的以 swagger、springfox、apidoc 开头的 URL,记录该 URL 并在权限拦截中放行

API文档生成之Springfox-Swagger框架的简单使用
image.png

例如 shiro,在 ShiroFilter 中放行以下 URL。注意不同版本的 swaggerURL 略有区别,请以上面的方式查看具体需放行的 URL

  1. filterChainDefinitionMap.put(“/swagger-ui.html”, “anon”);
  2. filterChainDefinitionMap.put(“/swagger-resources/**”, “anon”);
  3. filterChainDefinitionMap.put(“/v2/api-docs”, “anon”);
  4. filterChainDefinitionMap.put(“/webjars/springfox-swagger-ui/**”, “anon”);

前后分离之 Authorization 请求头设置

在前后分离项目中部分项目会使用到 jwt 或者其他方式在 header 中传递令牌 token 的方式,需要 swagger 添加相关配置加入请求头

  1. 在 swaggerConfig 中,加入全局 header 设置,参考如下代码
  1. /**
  2. * Created by wangqichang on 2018/9/25.
  3. */
  4. @Slf4j
  5. @Configuration
  6. @EnableSwagger2
  7. public class SwaggerConfig{
  8. @Value(“${SwaggerSwitch:false}”)
  9. private boolean SwaggerSwitch;
  10. @Bean
  11. public Docket createRestApi(){
  12. log.info(“========================================== 当前环境是否开启 Swagger:” + SwaggerSwitch);
  13. return new Docket(DocumentationType.SWAGGER_2)
  14. .securitySchemes(securitySchemes())
  15. .securityContexts(securityContexts())
  16. .enable(SwaggerSwitch)
  17. .apiInfo(apiInfo()).select()
  18. .apis(RequestHandlerSelectors.basePackage(“com.seebon.vip.csp.server.web.controller.rest”)).paths(PathSelectors.any())
  19. .build();
  20. }
  21. private ApiInfo apiInfo(){
  22. return new ApiInfoBuilder().title(“VIP 后台系统 API”).description(“新后台 Restful 接口”).termsOfServiceUrl(http://www.xxx.com&#8221;)
  23. .version(“1.0”).build();
  24. }
  25. /**
  26. * swagger 加入全局 Authorization header 将在 ui 界面右上角新增 token 输入界面
  27. * @return
  28. */
  29. private List<ApiKey> securitySchemes(){
  30. ApiKey apiKey = new ApiKey(“Authorization”, “Authorization”, “header”);
  31. ArrayList arrayList = new ArrayList();
  32. arrayList.add(apiKey);
  33. return arrayList;
  34. }
  35. /**
  36. * 在 Swagger2 的 securityContexts 中通过正则表达式,设置需要使用参数的接口(或者说,是去除掉不需要使用参数的接口),
  37. * 如下列代码所示,通过 PathSelectors.regex(“^(?!auth).*$”),
  38. * 所有包含”auth”的接口不需要使用 securitySchemes。即不需要使用上文中设置的名为“Authorization”,
  39. * type 为“header”的参数。
  40. *
  41. */
  42. private List<SecurityContext> securityContexts(){
  43. SecurityContext build = SecurityContext.builder()
  44. .securityReferences(defaultAuth())
  45. .forPaths(PathSelectors.any())
  46. .build();
  47. ArrayList arrayList = new ArrayList();
  48. arrayList.add(build);
  49. return arrayList;
  50. }
  51. List<SecurityReference> defaultAuth(){
  52. AuthorizationScope authorizationScope = new AuthorizationScope(“global”, “accessEverything”);
  53. AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
  54. authorizationScopes[0] = authorizationScope;
  55. SecurityReference authorization = new SecurityReference(“Authorization”, authorizationScopes);
  56. ArrayList arrayList = new ArrayList<>();
  57. arrayList.add(authorization);
  58. return arrayList;
  59. }
  60. }

加入以上代码后,swagger-ui 界面右上角将出现如图图标

API文档生成之Springfox-Swagger框架的简单使用
image.png

点击该图标,在 value 中输入登录返回的 token

API文档生成之Springfox-Swagger框架的简单使用
image.png

然后请求任何接口,都会带上一个名为 Authorization,值为输入值(例如本文中的 token)的请求头了

API文档生成之Springfox-Swagger框架的简单使用
image.png

踩坑记录

采坑 1
项目使用了 sitemesh 时,需要在 decorators.xml 中把上述资源进行排除,否则会影响到 swagger-ui 资源的访问,导致 swagger-ui 界面无内容

采坑 2
在自定义权限控制中,访问/swagger-resources/configuration/ui 资源时 404,原因为通过@EnableSwagger2 时会自动扫描到一个 ApiResourceController 类,该类有个资源路径为/configuration/ui
而在自定义权限控制中该路径被拦截返回错误导致 404


露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:API文档生成之Springfox-Swagger框架的简单使用
喜欢 (0)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址