前言:如果你是后台开发,提供restful接口给前端,建议你使用Swagger3提供restful的接口文档自动生成和在线接口调试。knife4j是对Swagger进一步封装,其优化了API文档的UI界面,是本人最推荐的方式。


一、Swagger简介

1.1、Swagger

Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。Swagger拥有接口文档自动生成和在线调试接口的两大功能。Swagger拥有众多不同语言和平台的开源实现与工具,他有很多实现方式,非常方便,并且支持语言特别多。

(1) Swagger是一组开源项目,其中主要项目如下:

  • Swagger-tools:提供各种与Swagger进行集成和交互的工具。例如模式检验、Swagger 1.2文档转换成Swagger 2.0文档等功能。

  • Swagger-core: 用于Java/Scala的的Swagger实现。与JAX-RS(Jersey、Resteasy、CXF...)、Servlets和Play框架进行集成。

  • Swagger-js: 用于JavaScript的Swagger实现。

  • Swagger-node-express: Swagger模块,用于node.js的Express web应用框架。

  • Swagger-ui:一个无依赖的HTML、JS和CSS集合,可以为Swagger兼容API动态生成优雅文档。

  • Swagger-codegen:一个模板驱动引擎,通过分析用户Swagger资源声明以各种语言生成客户端代码。

(2) springfox-swagger

在Spring中集成Swagger会使用到springfox-swagger,它对Spring和Swagger的使用进行了整合

springfox是Java对swagger的一个具体实现。springfox的前身是swagger-springmvc,用于springmvc与swagger的整合。它内部会自动解析Spring容器中Controller暴露出的接口,并且也提供了一个界面用于展示或调用这些API。

Springfox其实是一个通过扫描代码提取代码中的信息,生成API文档的工具。在Swagger的教程中,都会提到@Api@ApiModel@ApiOperation这些注解,这些注解其实不是Springfox的,而是Swagger的。springfox-swagger2这个包依赖了swagger-core这个包,而这些注解正是在这里面。但是,swagger-core这个包是只支持JAX-RS2的,并不支持常用的Spring MVC。这就是springfox-swagger的作用了,它将上面那些用于JAX-RS2的注解适配到了Spring MVC上。

Spring项目引入依赖:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${springfox.swagger.version}</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${springfox.swagger.version}</version>
</dependency>

SpingBoot项目引入依赖:

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>

(3)Swagger 3 的使用

Swagger2(基于openApi2)已经在17年停止维护了,取而代之的是 Swagger3(基于openApi3),而国内 Swagger3使用的文档较少,百度搜出来的都是过时的Swagger2(17年停止维护并更名为swagger3)的使用。

  • Open API:OpenApi是业界真正的 api 文档标准,其是由 Swagger 来维护的,并被linux列为api标准,从而成为行业标准。

  • Swagger组织:swagger 是一个 api 文档维护组织,后来成为了 Open API 标准的主要定义者,现在最新的版本为17年发布的 Swagger3(Open Api3)。

  • swagger2的包名为 io.swagger,而swagger3的包名为 io.swagger.core.v3。

Swagger 3 相关特性:

  • 支持 Spring 5,Webflux(仅请求映射支持,尚不支持功能端点)、Spring Integration

  • 补充官方在 spring boot 的自动装配 springfox-boot-starter 以后可以直接依赖一个 dependency

  • 与2.0更好的规范兼容性

  • 支持OpenApi 3.0.3

  • 轻依赖 spring-plugin,swagger-core

  • 现有的swagger2批注将继续有效并丰富开放式API 3.0规范

1.2、springfox-swagger-ui

如果使用springfox-swagger-ui,启动项目后的api文档访问路径:http://localhost:8080/swagger-ui.html

 1.3、swagger-bootstrap-ui

swagger-bootstrap-ui是springfox-swagger的增强UI实现,api文档结构更加清晰,在线调试也很方便

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>${swagger.bootstrap.ui.version}</version>
</dependency>

访问的url为:http:// http://localhost:8080/doc.html

1.4、swagger-bootstrap-ui的升级版Knife4j

Knife4j在更名之前,原来的名称是叫swagger-bootstrap-ui,这是两种不一样风格的ui显示,将原来的蓝色变成炫酷的黑色模式,比传统的springfox-swagger-ui和swagger-bootstrap-ui更好看Knife4j是本人最推荐的方式

  • Knifej是使用knife4j-spring-boot-starter的风格来编写的,可以将配置项写在配置文件中,这些配置项提供了许多增强功能,可以更好的整合springboot、springcloud;

  • Knifej执行更新,为了更平滑的演进,而swagger-bootstrap-ui已停更;

 1.5、SpringDoc(可选)

SpringDoc也是 Spring 社区维护的一个项目(非官方),帮助使用者将 swagger3 集成到 Spring 中。也是用来在 Spring 中帮助开发者生成文档,并可以轻松的在SpringBoot中使用。SpringDoc基于swagger,并做了更多的对Spring系列各框架的兼容,用法上与Swagger3基本相同,并多了一些自己的配置,相较于Swagger3来说更好用,支持也更好一点。

从 spring-fox 迁移到 springdoc,需要依赖变更:pom.xml 里去掉 springfox 或者 swagger 的依赖,并添加springdoc-openapi-ui

   <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-ui</artifactId>
      <version>1.3.1</version>
   </dependency>

SpringDoc使用 swagger3 注解代替 swagger2 的:

这一步是可选的,因为改动太大,故 springfox对旧版的 swagger做了兼容处理。但不知道未来会不会不兼容,这里列出如何用 swagger 3 的注解代替 swagger 2 的,注意修改 swagger 3 注解的包路径为io.swagger.v3.oas.annotations。

Swagger2 的注解命名以易用性切入,全是 Api 开头,在培养出使用者依赖注解的习惯后,Swagger 3将注解名称规范化和工程化:

如果感兴趣,可以参考:SpringBoot结合SpringDo


二、Knife4j代替springfox-boot-starter实现Swagger3

Swagger用来自动生成API接口文档,还可以在线调试;而knife4j是对Swagger进一步封装,其优化了API文档的UI界面。Knife4j的前身是swagger-bootstrap-ui取名knife4j是希望她能像一把匕首一样小巧轻量并且功能强悍,希望把她做成一个为Swagger接口文档服务的通用性解决方案,不仅仅只是专注于UI前端。

2.1、导入Knife4j依赖

  •  SpringFox是对Swagger的SpringMVC的支持融合,Knife4是对Swagger进一步封装,其优化了api文档的界面

  •  Knife4j在更名之前,原来的名称是叫swagger-bootstrap-ui。Knife4j底层依赖springfox,因此无需再单独引入springfox的具体版本

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

2.2、创建Knife4jConfig配置类

import com.hs.notice.entity.EmailNotice;
import com.hs.notice.entity.SmsNotice;
import com.hs.notice.entity.WeChatNotice;
import com.fasterxml.classmate.TypeResolver;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * 作用: 自动生成API文档和在线接口调试工具
 */

@Configuration
//该注解是Springfox-swagger框架提供的使用Swagger注解,该注解必须加
@EnableSwagger2
//knife4j提供的增强扫描注解,Ui提供了例如动态参数、参数过滤、接口排序等增强功能
@EnableKnife4j
public class Knife4jConfig {

    /**
     *     创建一个Docket的对象,相当于是swagger的一个实例 : 配置开发和测试环境下开启Swagger,生产发布时关闭
     *
     *     RequestHandlerSelectors,配置要扫描接口的方式
     *     basePackage:指定扫描的包路径
     *     any:扫描全部
     *     none:全部不扫描
     *     withClassAnnotation:扫描类上的注解,如RestController
     *     withMethodAnnotation:扫描方法上的注解,如GetMapping
     *
     * @return
     */
    @Autowired
    TypeResolver typeResolver;
    @Bean
    public Docket createRestApi(Environment environment)
    {
        //设置显示的swagger环境信息,判断是否处在自己设定的环境当中,为了安全生产环境不开放Swagger
        Profiles profiles=Profiles.of("dev","test");
        boolean flag=environment.acceptsProfiles(profiles);
        //创建一个Docket的对象,相当于是swagger的一个实例
        return new Docket(DocumentationType.SWAGGER_2)
                .useDefaultResponseMessages(false)
                .groupName("1.x版本")
                .apiInfo(apiInfo())
                //只有当springboot配置文件为dev或test环境时,才开启swaggerAPI文档功能
                .enable(flag)
                .select()
                // 这里指定Controller扫描包路径:设置要扫描的接口类,一般是Controller类
                .apis(RequestHandlerSelectors.basePackage("com.hs.notice.controller"))  //这里采用包扫描的方式来确定要显示的接口
//                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //这里采用包含注解的方式来确定要显示的接口
                // 配置过滤哪些,设置对应的路径才获取
                .paths(PathSelectors.any())
                .build()
                //防止Controller中参数中没有实体类或者返回值不是实体类导致Swagger Models页面扫描不到的情况
                .additionalModels(typeResolver.resolve(EmailNotice.class))
                .additionalModels(typeResolver.resolve(SmsNotice.class))
                .additionalModels(typeResolver.resolve(WeChatNotice.class));
    }

    ///配置相关的api信息
    private ApiInfo apiInfo()
    {
        return new ApiInfoBuilder()
                .description("API调试文档")
                //作者信息
                .contact(new Contact("何哥", "http://ip地址:8086/doc.html", "110@qq.com"))
                .version("v1.0")
                .title("消息通知服务API文档")
                //服务Url
                .termsOfServiceUrl("")
                .build();
    }
}

如上代码所示,通过@Configuration注解,让Spring来加载该类配置。

  • @ EnableSwagger2支持Swagger 2的SpringFox支持。
  • DocumentationType.SWAGGER_2告诉Docketbean我们正在使用Swagger规范的版本2。

  • apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。

  • select()创建一个构建器,用于定义哪些控制器及其生成的文档中应包含哪些方法。

  • apis()定义要包含的类(控制器和模型类)。这里我们包括所有这些,但您可以通过基础包,类注释等来限制它们。SpringFox会将其检测为文档生成源。Controller和Model类。您可以在Docket配置中轻松配置它。我们可以使用.apis(RequestHandlerSelectors.any(来包含所有类;当然我们也可以缩小到我们的基础包。

  • paths()允许您根据路径映射定义应包含哪个控制器的方法。我们现在包括所有这些,但您可以使用正则表达式等限制它。上面的代码.paths(PathSelectors.any())是代表匹配所有URL。

2.3、创建User实体类

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(value = "用户实体")
public class User {

    @ApiModelProperty(value = "id")
    private Integer id;

    @ApiModelProperty(value = "用户名")
    private  String username;

    @ApiModelProperty(value = "性别,0男,1女")
    private Integer sex;
}

 2.4、创建UserController接口

package com.example.demo.controller;

import com.example.demo.User;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

@RestController
@Api(tags = "用户接口")//描述UserController的信息
public class UserController {
 
    @ApiOperation(value = "查询用户",notes = "根据id查询用户")
    @ApiImplicitParam(paramType = "path",name="id",value = "用户id",required = true)
    @GetMapping("/user/query/{id}")
    public String getUserById(@PathVariable Integer id) {
        return "/user/"+id;
    }

    @ApiResponses({
            @ApiResponse(code=200,message="删除成功"),
            @ApiResponse(code=500,message="删除失败")})
    @ApiOperation(value = "删除用户",notes = "根据id删除用户")
    @DeleteMapping("/user/delete/{id}")
    public Integer deleteUserById(@PathVariable Integer id) {
        return id;
    }

    @ApiOperation(value = "添加用户",notes = "添加一个用户,传入用户名和性别")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query",name="username",value = "用户名",required = true,defaultValue = "张三"),
            @ApiImplicitParam(paramType = "query",name="sex",value = "性别",required = true,defaultValue = "女")
    })
    @PostMapping("/user")
    public String addUser(@RequestParam String username,@RequestParam String sex){
        return username+","+sex;
    }

    @ApiOperation(value="修改用户",notes = "根据传入的用户信息修改用户")
    @PutMapping("/user")
    public String updateUser(@RequestBody User user){
        return user.toString();
    }

    @GetMapping("/ignore")
    @ApiIgnore
    public void ignoreMethod(){}

}

2.5、启动项目,访问Swagger文档

启动项目,在浏览器输入http://localhost:8080/doc.html就可以看到接口的信息,展开接口,就能看到所有的接口详细信息。

展开后可以对各个请求进行测试。选择接口后点击调试,输入相关的参数点击发送按钮即可。 

 页面简单清爽 ,可以导出接口文档非常方便,赶紧来试试吧


三、Swagger常用注解

@Api:用在请求的类上,表示对类的说明
    tags="说明该类的作用,可以在UI界面上看到的注解"
    value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
 
@ApiOperation:用在请求的方法上,说明方法的用途、作用
    value="说明方法的用途、作用"
    notes="方法的备注说明"
 
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
    @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
        name:参数名
        value:参数的汉字说明、解释
        required:参数是否必须传
        paramType:参数放在哪个地方
            · header --> 请求参数的获取:@RequestHeader
            · query --> 请求参数的获取:@RequestParam
            · path(用于restful接口)--> 请求参数的获取:@PathVariable
            · body(不常用)
            · form(不常用)    
        dataType:参数类型,默认String,其它值dataType="Integer"       
        defaultValue:参数的默认值
 
@ApiResponses:用在请求的方法上,表示一组响应
    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
        code:数字,例如400
        message:信息,例如"请求参数没填好"
        response:抛出异常的类
 
@ApiModel:用于响应类上,表示一个返回响应数据的信息
            (这种一般用在post创建的时候,使用@RequestBody这样的场景,
            请求参数无法使用@ApiImplicitParam注解进行描述的时候)
    @ApiModelProperty:用在属性上,描述响应类的属性

3.1、Model类使用

@Data
@ApiModel(value="BizComponent对象", description="组件")
public class BizComponent implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "分类")
    private Long categoryId;

    @ApiModelProperty(value = "组件名称")
    private String name;

    @ApiModelProperty(value = "组件描述")
    private String description;

    @ApiModelProperty(value = "日期字段")
    private LocalDateTime componentTime;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @ApiModelProperty(value = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime modifiedTime;
}

3.2、Control类和接口方法使用

@RestController
@RequestMapping("/api/category")
@Api(value = "/category", tags = "组件分类")
public class BizCategoryController {

    private IBizCategoryService bizCategoryService;

    @GetMapping("/list")
    @ApiOperation(value = "列表", notes = "分页列表")
    public R<PageModel<BizCategory>> list(PageQuery pageQuery,
                                          @RequestParam @ApiParam("组件分类名称") String name) {
        IPage<BizCategory> page = bizCategoryService.page(pageQuery.loadPage(),
                new LambdaQueryWrapper<BizCategory>().like(BizCategory::getName, name));
        return R.success(page);
    }

    @GetMapping("/{categoryId}")
    @ApiOperation(value = "详情", notes = "组件分类详情")
    @ApiResponses({
            @ApiResponse(code=200,message="请求成功"),
            @ApiResponse(code=100,message="请求失败")})
    public R<BizCategory> detail(@PathVariable @ApiParam("分类Id") Long categoryId) {
        BizCategory category = bizCategoryService.getById(categoryId);
        return R.success(category);
    }

    @PostMapping("/save")
    @ApiOperation(value = "保存", notes = "新增或修改")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "form", name = "categoryId", value = "组件id(修改时为必填)"),
            @ApiImplicitParam(paramType = "form", name = "name", value = "组件分类名称", required = true)
    })
    public R<BizCategory> save(Long categoryId, String name) {
        BizCategory category = new BizCategory();
        category.setId(categoryId);
        category.setName(name);
        bizCategoryService.saveOrUpdate(category);
        return R.success(category);
    }

    @DeleteMapping("/{categoryId}")
    @ApiOperation(value = "删除", notes = "删除")
    public R delete(@PathVariable @ApiParam("分类Id") Long categoryId) {
        bizCategoryService.delete(categoryId);
        return R.success();
    }
}

四、企业项目中比较实用的用法

4.1、忽略不想生成文档的接口

某些Controller 不需要生成API文档的接口,可以通过@ApiIgnore忽略掉

 4.2、开发环境开启Swagger ,生产环境关闭

虽然说swagger是个好东西,但是使用中切不可以忽略的一个安全问题。dev环境中你可以开放swagger给前端或者测试,但如果你的swagger ui不小心放到了生产,那是一件多么可怕的事情,真可以来个一锅端,切记切记。

    @Value("${swagger.switch}")
    private boolean swaggerSwitch;

    @Bean
    public Docket api() {
        Docket docket = new Docket(DocumentationType.SWAGGER_2);
        if (swaggerSwitch) {
            docket.enable(true);
        } else {
            docket.enable(false);
        }

        docket.apiInfo(apiInfo()).select().paths(PathSelectors.any()).build();

        return docket;
    }

4.3、Swagger UI中 model实体类不显示解决方案

方式一:只要在接口中,返回值中存在实体类,就会被扫描到swagger中

  @PostMapping("/account")
    public Account user(){
        return new Account();
    }

方式二:在controller 使用 @RequestBody 注解

Swagger的model里面之所以没有你需要的model,是因为你的model没有被swagger发现,我们利用Spring的注解让这个类被发现就行

   @ApiOperation(value = "添加用户", notes = "添加新用户")
    @PostMapping("/add")
    public Map addUser( @RequestBody Account account){
        return userService.createUser(account);
    }

方式三:在Swagger的配置Bean中,手动的添加你想呈现的类,如下最后三行,可以写很多个typeResolver.resolve(XXX.class)作为参数传入

   import com.fasterxml.classmate.TypeResolver;

    @Autowired
    TypeResolver typeResolver;
    @Bean
    public Docket createRestApi(Environment environment)
    {
        return new Docket(DocumentationType.SWAGGER_2)
                .useDefaultResponseMessages(false)
                .groupName("1.x版本")
                .apiInfo(apiInfo())
                .enable(true)
                .select()
               .apis(RequestHandlerSelectors.basePackage("com.hs.notice.controller"))  
                .paths(PathSelectors.any())
                .build()
                //防止Controller中参数中没有实体类或者返回值不是实体类导致Swagger Models页面扫描不到的情况
                .additionalModels(typeResolver.resolve(EmailNotice.class))
                .additionalModels(typeResolver.resolve(SmsNotice.class))
                .additionalModels(typeResolver.resolve(WeChatNotice.class));
    }

4.4、Knife4j文档请求异常

 报错信息如下:

java.lang.NullPointerException: null
    at springfox.documentation.swagger2.mappers.RequestParameterMapper.bodyParameter(RequestParameterMapper.java:264) ~[springfox-swagger2-3.0.0.jar:3.0.0]

错误原因:

极有可能就是,给参数变量重命名了,导致Controller入参和API写的参数对应不上

比如下面代码中的Api提示参数的name = "errorDeviceState",但实际参数写的却是 errorDeviceStateEnum

@ApiOperation("查看单个完整巡检报告")
@GetMapping
@ApiImplicitParams({
            @ApiImplicitParam(name = "inspectId", value = "巡检任务ID", dataTypeClass = String.class),
            @ApiImplicitParam(name = "errorDeviceState", value = "设备巡检状态(1异常,2离线)", dataTypeClass = ErrorDeviceStateEnum.class)
    })
public WebResult<ReportVO> queryDetail(@RequestParam String inspectId, @RequestParam ErrorDeviceStateEnum errorDeviceStateEnum) {
     // code ...
}

4.5、Swagger文档中的调试界面请求参数丢失

问题描述:请求参数本来应该是x-www-form-urlencoded方式的,却变成了raw方式,丢失content参数

  解决方案:丢失参数content前面加上个@RequestParam注解就可以了

sendMessage(String openid,@RequestParam @Length(max=1024) String content,String appId,String t_sign,String requestId)

4.6、本地swagger使用localhost/doc.html不能访问

localhost/doc.html换为127.0.0.1/doc.html


参考链接:

knife4j官网

SpringBoot整合knife4j

swagger2 注解说明 ( @ApiImplicitParams )

knife4j及一些api注解说明

SpringBoot项目整合knife4j 2.0总结

SpringBoot集成Swagger3.0

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐