SpringBoot2.7.4集成Knife4j3.0.3实战

版本信息

一、pom.xml引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.4</version>
    <relativePath/>
</parent>
...
<!-- knife4j -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-data-rest</artifactId>
    <version>3.0.0</version>
</dependency>

二、yml中配置

# Knife4j配置
knife4j:
  enable: true # 开启增强配置(增强功能需要通过配置yml配置文件开启增强,自2.0.7开始)

三、Knife4jConfig配置

StatusCode类见附录A

package cn.keyidea.common.config;

import cn.keyidea.common.constant.StatusCode;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Response;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * Knife4j配置
 *
 * @author qyd
 * @date 2022-10
 */
@Configuration
@EnableSwagger2
@EnableKnife4j
public class Knife4jConfig {

    // 定义分隔符,配置Swagger多包
    private static final String SPLITOR = ";";
    private static final String SERVICE_URL = "http://127.0.0.1:7004/tj4/doc.html";
    private static final String API_INFO_TITLE = "软件接口文档";
    private static final String API_INFO_DESCRIPTION = "Api接口列表";
    private static final String API_INFO_LICENSE = "2024年度内部文档,违拷必究.";

    @Order(4)
    @Bean
    public Docket createRestApi4() {
        // 添加全局响应状态码
        List<Response> responseMessageList = new ArrayList<>();

        // 根据 StatusCode 获取自定义响应码
        Arrays.stream(StatusCode.values()).forEach(
                errorEnums -> responseMessageList.add(new ResponseBuilder()
                        .code(errorEnums.getCode())
                        .description(errorEnums.getMsg())
                        .build()));

        Docket build = new Docket(DocumentationType.SWAGGER_2)
                // 添加全局响应状态码,可根据不同系统定义不同的响应码信息
                .globalResponses(HttpMethod.GET, responseMessageList)
                .globalResponses(HttpMethod.PUT, responseMessageList)
                .globalResponses(HttpMethod.POST, responseMessageList)
                .globalResponses(HttpMethod.DELETE, responseMessageList)
                .apiInfo(apiInfo())
                .groupName("2024集同接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.keyidea.second"))
                .build();
        return build;
    }

    @Order(3)
    @Bean
    public Docket createRestApi3() {
        // 添加全局响应状态码
        List<Response> responseMessageList = new ArrayList<>();

        // 根据 StatusCode 获取自定义响应码
        Arrays.stream(StatusCode.values()).forEach(
                errorEnums -> responseMessageList.add(new ResponseBuilder()
                        .code(errorEnums.getCode())
                        .description(errorEnums.getMsg())
                        .build()));

        Docket build = new Docket(DocumentationType.SWAGGER_2)
                // 添加全局响应状态码,可根据不同系统定义不同的响应码信息
                .globalResponses(HttpMethod.GET, responseMessageList)
                .globalResponses(HttpMethod.PUT, responseMessageList)
                .globalResponses(HttpMethod.POST, responseMessageList)
                .globalResponses(HttpMethod.DELETE, responseMessageList)
                .apiInfo(apiInfo())
                .groupName("2023集同接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.keyidea.control"))
                .build();
        return build;
    }

    @Order(2)
    @Bean
    public Docket createRestApi2() {
        // 添加全局响应状态码
        List<Response> responseMessageList = new ArrayList<>();

        // 根据 StatusCode 获取自定义响应码
        Arrays.stream(StatusCode.values())
                .forEach(errorEnums -> responseMessageList.add(new ResponseBuilder()
                        .code(errorEnums.getCode())
                        .description(errorEnums.getMsg())
                        .build()));

        Docket build = new Docket(DocumentationType.SWAGGER_2)
                // 添加全局响应状态码,可根据不同系统定义不同的响应码信息
                .globalResponses(HttpMethod.GET, responseMessageList)
                .globalResponses(HttpMethod.PUT, responseMessageList)
                .globalResponses(HttpMethod.POST, responseMessageList)
                .globalResponses(HttpMethod.DELETE, responseMessageList)
                .apiInfo(apiInfo())
                .groupName("业务接口文档")
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.keyidea.business"))
                .build();
        return build;
    }

    @Order(1)
    @Bean
    public Docket createRestApi1() {
        // 添加全局响应状态码
        List<Response> responseMessageList = new ArrayList<>();

        // 根据 StatusCode 获取自定义响应码
        Arrays.stream(StatusCode.values()).forEach(
                errorEnums -> responseMessageList.add(new ResponseBuilder()
                        .code(errorEnums.getCode())
                        .description(errorEnums.getMsg())
                        .build()));

        Docket build = new Docket(DocumentationType.SWAGGER_2)
                // 添加全局响应状态码,可根据不同系统定义不同的响应码信息
                .globalResponses(HttpMethod.GET, responseMessageList)
                .globalResponses(HttpMethod.PUT, responseMessageList)
                .globalResponses(HttpMethod.POST, responseMessageList)
                .globalResponses(HttpMethod.DELETE, responseMessageList)
                .apiInfo(apiInfo())
                .groupName("系统接口文档")
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.keyidea.sys"))
                // .apis(basePackage("cn.keyidea.business" + SPLITOR + "cn.keyidea.sys"))
                .build();
        return build;
    }


    /**
     * 构建 api文档的详细信息函数,注意这里的注解引用的是哪个
     *
     * @return 返回apiInfo对象
     * @link {https://www.cnblogs.com/zs-notes/p/10845741.html}
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                // 文档标题
                .title(API_INFO_TITLE)
                // 文档描述
                .description(API_INFO_DESCRIPTION)
                // 创建人信息
                .contact(new Contact("Keyidea", "http://keyidea.cn", "support@keyidea.cn"))
                // 文档版本号
                .version("1.0.0")
                // 认证许可
                .license(API_INFO_LICENSE)
                // 设置服务Url
                .termsOfServiceUrl(SERVICE_URL)
                .build();
    }

    /**
     * 配置多个扫描包(扫描多个包时,多个包的控制器会在同一个接口文档中呈现)
     *
     * @param basePackage 不同的包,使用分号隔开
     * @return 返回结果
     * @link {knife4j的简单使用(二)-配置多个扫描包 https://blog.csdn.net/hs_shengxiaguangnian/article/details/110849098}
     */
    public static Predicate<RequestHandler> basePackage(final String basePackage) {
        return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
    }

    private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
        return input ->
        {
            // 循环判断匹配
            for (String strPackage : basePackage.split(SPLITOR)) {
                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.fromNullable(input.declaringClass());
    }
}

说明:虽然引入的是Knife3.0.3的版本,但是在KnifeConfig配置中仍采用的是Swagger2规范,请注意;具体Knife4j版本选择,请见Knife4j版本参考 | Knife4j

四、ShiroConfig中放行Swagger相关路径

如果SpringBoot未集成Shiro,那么此处无需关注。

...
// Shiro放行swagger2(Knife4j)
filterMap.put("/doc.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/v2/**", "anon");
filterMap.put("/webjars/**", "anon");
...

五、注解说明

注解 功能 模块
@Api 用于对整个控制器类进行描述,指定一些全局信息,如分组、描述等。
- tags:指定分组,用于在文档中对接口进行分类展示
- description:对整个控制器的描述
控制器
@ApiSort 分组排序;值越小,越靠前
控制器
@ApiOperationSupport 用于对单个接口方法进行描述,指定该接口所属作者,接口排序号,忽略请求时影藏的参数等
- order:排序
- author:开发者
-ignoreParameters:忽略的请求参数
接口方法
@ApiOperation 用于对单个接口方法进行描述,指定该接口的一些信息,如标题、说明等。
- value:接口的标题。
- notes:接口的详细说明。
接口方法
@ApiParam 用于对接口方法的参数进行描述,指定参数的一些信息,如名称、是否必须、描述等。
- value:参数的描述
- required:指定参数是否是必须的
接口方法
@ApiModel 用于对模型类进行描述,指定模型的一些信息,如描述、子类等。
- description:模型的描述
模型类
@ApiModelProperty 用于对模型类的属性进行描述,指定属性的一些信息,如描述、示例值等
- value:属性的描述
- example:属性的示例值
模型类

六、典型应用

1.文件上传

package cn.keyidea.sys.controller;

import cn.keyidea.common.bean.BaseRes;
import cn.keyidea.sys.service.SysFileService;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;

/**
 * 系统公共类
 *
 * @author qyd
 * @date 2022-10-17
 */
@ApiSort(1)
@Api(tags = "1-系统公共类", value = "系统公共类")
@RestController
@RequestMapping("/sys/common/")
public class CommonController {

    private final static Logger logger = LoggerFactory.getLogger(CommonController.class);

    @Autowired
    private SysFileService sysFileService;

    @ApiOperationSupport(author = "qyd", order = 1)
    @ApiOperation(value = "文件上传", notes = "")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "file", value = "单文件上传", required = true, paramType = "formData", dataType = "File", dataTypeClass = File.class),
            @ApiImplicitParam(name = "fileType", value = "文件类型", required = true, defaultValue = "txt", example = "txt", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "type", value = "是否使用文件原始名称:1-使用,其他-不使用(使用随机UUID)", required = false, defaultValue = "1", example = "1", dataType = "Integer", dataTypeClass = Integer.class)
    })
    @PostMapping("uploadFile")
    public BaseRes uploadFile(@RequestPart(value = "file", required = true) MultipartFile file,
                              @RequestParam(value = "fileType", required = true) String fileType,
                              @RequestParam(value = "type", required = false) Integer type) {
        try {
            return sysFileService.uploadFile(file, fileType, type);
        } catch (Exception e) {
            return BaseRes.invalidParam("文件上传失败");
        }
    }
}

2、实体类(分页参数基类PageObject

package cn.keyidea.common.bean;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import javax.validation.constraints.NotNull;
import java.io.Serializable;

/**
 * 分页基类 分页参数对象
 *
 * @author qyd
 * @date 2024-06-05
 */
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel(value = "PageObject", description = "分页对象")
public class PageObject implements Serializable {

    // 前端实际使用天基三期(React)写法当前页使用的是page
    @NotNull(message = "当前页不能为NULL")
    @ApiModelProperty(value = "当前页,默认1", required = true, example = "1")
    private Integer page;

    @NotNull(message = "分页数不能为NULL")
    @ApiModelProperty(value = "分页数,默认15", required = true, example = "15")
    private Integer pageSize;

    @ApiModelProperty(value = "排序字段", required = false, example = "")
    private String orderBy;

    @ApiModelProperty(value = "排序方式:false-asc,true-desc", required = false, example = "false")
    private Boolean desc;

}

七、FAQ

访问地址示例

http://ip:port/doc.html,如果SpringBoot配置了根路由,需要在doc.html前面添加即可,如根路径为/tj4(server.servlet.context-path),则Swagger访问路径为:http://ip:port/tj4/doc.html

附录

附录A:状态码枚举定义类(StatusCode.java)

package cn.keyidea.common.constant;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 状态码枚举定义
 *
 * @author qyd
 * @date 2022-10-13
 */
public enum StatusCode
{

    SUCCESS(1000, "请求成功"),
    INVALID_PARAM(1001, "非法字段"),
    SYSTEM_BUSY(1002, "系统忙"),
    INVALID_MASTER_KEY(1003, "无接口访问权限"),
    FAILURE(1004, "请求失败"),
    UNAUTHORIZED(1005, "未授权"),
    INVALID_TOKEN(2001, "TOKEN失效"),
    CONNECT_TIMED_OUT(3001, "请求超时"),
    HTTP_REQ_ERROR(3002, "HTTP请求出错");

    /**
     * 错误码
     */
    private final int code;
    /**
     * 错误描述信息
     */
    private final String msg;

    StatusCode(int code, String msg)
    {
        this.code = code;
        this.msg = msg;
    }

    public String getMsg()
    {
        return this.msg;
    }

    public String getCode()
    {
        return this.code + "";
    }

    public int getCodeValue()
    {
        return this.code;
    }

    /**
     * 转为Map集合数据
     *
     * @return 枚举对象Map集合
     */
    public static Map<Integer, String> toMap()
    {
        Map<Integer, String> map = new HashMap<>(32);
        for (StatusCode value : StatusCode.values())
        {
            map.put(value.getCodeValue(), value.getMsg());
        }
        return map;
    }

    /**
     * 转为List集合数据
     *
     * @return 枚举对象List集合
     */
    public static List<Map<String, String>> toList()
    {
        List<Map<String, String>> list = new ArrayList<>(32);
        Map<String, String> map = null;
        for (StatusCode item : StatusCode.values())
        {
            map = new HashMap<>();
            map.put("code", item.getCode());
            map.put("msg", item.getMsg());
            list.add(map);
        }
        map = null;
        return list;
    }
}

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容