使用 Knife4j(Swagger)工具自动生成 API 接口文档

发布时间 2023-05-03 21:30:09作者: 乔京飞

现在的项目开发,绝大多数都已经采用前后端分离,前后端开发人员必须依靠接口文档进行协作。当前最流行的文档生成工具就是 Swagger,它是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。但是本篇博客介绍的是 Knife4j ,它是集 Swagger 和 OpenAPI 为一体的增强解决方案,拥有更多更强大的功能。本篇博客通过做 Demo 演示如何使用 Knife4j ,在本篇博客的最后会提供源代码下载。

Swagger 官网地址:https://swagger.io

Knife4j 官网地址:https://doc.xiaominfo.com


一、搭建工程

搭建一个 SpringBoot 工程,具体结构如下:

image

如果想使用 Knife4j ,首先需要在 pom 文件中引入 knife4j-spring-boot-starter 依赖包。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
    </parent>
    <groupId>com.jobs</groupId>
    <artifactId>springboot_knife4j</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
        <!--使用 knife4j 功能,只引入这一个依赖包即可-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.5</version>
            </plugin>
        </plugins>
    </build>
</project>

下面列出 application.yml 配置文件的内容:

server:
  port: 8888
knife4j:
  # 是否启用增强版功能
  enable: true
  # 如果是生产环境,将此设置为 true,然后就能够禁用了 knife4j 的页面
  production: false

Knife4j 的一个非常不错的功能就是可以通过 knife4j.production 配置是否是生产环境,如果配置为 true 的话,那么就不会展示出接口文档的页面,确保生产环境的安全性。当然有关 knife4j 还有很多其它的实用配置项,详情可以参考官网。

Knife4j 在运行过程中,会自动生成一个 doc.html 静态页面,这个就是我们要访问的接口文档页面。默认情况下 SpringBoot 是不允许访问静态资源的,因此我们需要在 SpringBoot 中配置 Knife4j 的静态资源请求映射路径。

另外需要使用 @EnableOpenAPI 注解。由于我们的接口都是在 controller 类中进行编写,因此需要配置 Knife4j 需要扫描的 controller 包。本博客的具体代码细节都编写在了 WebMvcConfig 类中了,具体细节如下:

package com.jobs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

//需要添加这个注解 @EnableOpenApi
@EnableOpenApi
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    //需要配置 knife4j 的静态资源请求映射地址
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Bean
    public Docket createDocket() {
        // 文档类型
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.jobs.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("我的测试")
                .version("1.0")
                .description("我的测试接口文档")
                .build();
    }
}

在实际的项目中,绝大多数情况下,网站必须登录后才能查看页面,为了能够匿名查看 knife4j 的接口文档页面,我们必须放行 Knife4j 的静态资源文件。本篇博客采用自己编写的 filter 来验证用户登录,因此需要在 filter 中放行 Knife4j 的静态资源文件。

package com.jobs.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jobs.entity.Result;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "LoginCheckFilter", urlPatterns = "/*")
public class LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String requestURI = request.getRequestURI();
        if (checkPassUri(requestURI)) {
            filterChain.doFilter(request, response);
            return;
        }

        Object uid = request.getSession().getAttribute("user");
        if (uid != null) {
            filterChain.doFilter(request, response);
            return;
        }

        response.getWriter().write(new ObjectMapper().writeValueAsString(Result.fail(-99,"no login")));
    }

    //路径匹配对象
    private static final AntPathMatcher apm = new AntPathMatcher();

    private boolean checkPassUri(String requestURI) {
        String[] uris = new String[]{
                //放行用户登录接口
                "/user/login",
                //放行用户退出接口
                "/user/logout",
                //放行下面的 knifefj 的静态资源文件路径
                "/doc.html",
                "/webjars/**",
                "/swagger-resources",
                "/v2/api-docs"
        };

        for (String uri : uris) {
            boolean match = apm.match(uri, requestURI);
            if (match) {
                return true;
            }
        }

        return false;
    }
}

为了能够是 filter 生效,需要在 SpringBoot 启动类上增加 @ServletComponentScan 注解。

package com.jobs;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@Slf4j
@ServletComponentScan
@SpringBootApplication
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class, args);
        log.info("项目启动成功");
    }
}

OK,项目结构已经搭建完毕了,下面就看具体的代码吧。


二、代码细节查看

其实现在启动 SpringBoot 程序,访问 localhost:8888/doc.html 就已经可以看到接口文档了。但是文档中没有任何注释,为了能够让接口文档更容易看懂,需要在代码中增加以下相关的注解。

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

下面就让我们把上面的注解,添加到具体的代码中,详情可下载源代码查看。

1 在实体类上使用 @ApiModel 和 @ApiModelProperty 注解

package com.jobs.entity;

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

import java.io.Serializable;

@Data
@ApiModel("用户")
public class User implements Serializable {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名称")
    private String name;

    @ApiModelProperty("用户年龄")
    private Integer age;
}
package com.jobs.entity;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;

@Data
@ApiModel("订单")
public class Order implements Serializable {

    @ApiModelProperty("订单id")
    private Long id;

    @ApiModelProperty("订单名称")
    private String name;

    @ApiModelProperty("订单价格")
    private BigDecimal price;

    @ApiModelProperty("订单数量")
    private Integer num;
}

2 在 Controller 上使用 @Api 注解,在接口上使用 @ApiOperation、@ApiImplicitParams 和 @ApiImplicitParam 注解

package com.jobs.controller;

import com.jobs.entity.Result;
import com.jobs.entity.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
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.RestController;
import javax.servlet.http.HttpServletRequest;

@Api(tags = "用户操作相关接口")
@RequestMapping("/user")
@RestController
public class UserController {

    @ApiOperation("用户登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name", value = "用户名", required = true),
            @ApiImplicitParam(name = "pwd", value = "密码", required = true)
    })
    @PostMapping("/login")
    public Result<String> login(String name, String pwd, HttpServletRequest request) {
        if ("jobs".equals(name) && "123".equals(pwd)) {
            request.getSession().setAttribute("user", "jobs");
            return Result.success("登录成功");
        } else {
            return Result.fail(-1, "用户名或密码不正确");
        }
    }

    @ApiOperation("用户退出")
    @PostMapping("/logout")
    public Result<String> logout(HttpServletRequest request) {
        request.getSession().removeAttribute("user");
        return Result.success("退出成功");
    }

    @ApiOperation("添加用户")
    @PostMapping
    public Result<User> Add(@RequestBody User user) {
        return Result.success(user);
    }

}
package com.jobs.controller;

import com.jobs.entity.Order;
import com.jobs.entity.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;

@Api(tags = "订单操作相关接口")
@RequestMapping("/order")
@RestController
public class OrderController {

    @ApiOperation("添加订单")
    @PostMapping
    public Result<Order> add(@RequestBody Order order) {
        return Result.success(order);
    }

    @ApiOperation("根据id获取订单")
    @ApiImplicitParam(name = "id", value = "订单id", required = true)
    @GetMapping("/{id}")
    public Result<Order> get(@PathVariable("id") Long id) {
        Order order = new Order();
        order.setId(id);
        order.setName("订单" + new Random().nextInt());
        order.setPrice(new BigDecimal(new Random().nextInt(200))
                .divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP));
        order.setNum(new Random().nextInt(100));
        return Result.success(order);
    }

    @ApiOperation("修改订单")
    @PutMapping
    public Result<String> update(@RequestBody Order order) {
        return Result.success("修改成功");
    }

    @ApiOperation("删除订单")
    @ApiImplicitParam(name = "id", value = "订单id", required = true)
    @DeleteMapping
    public Result<String> delete(Long id) {
        return Result.success("删除成功");
    }
}

OK,主要的代码已经添加好注解了,下面就可以启动 SpringBoot 程序,验证一下效果了。


三、验证效果

启动 SpringBoot 程序,访问 localhost:8888/doc.html 就可以看到文档接口页面了。

image

点击具体一个接口,可以查看接口详细信息:

image

可以进行接口的调用测试:

image

knife4j 还提供了导出离线文档的功能,如导出为 html 、markdown 以及 word 文档:

image


本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_knife4j.zip