版本信息
- JDK 1.8
- Maven 3.3.9
- SpringBoot 2.7.4~2.7.18
- [Knife4j 3.0.3](https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-spring-boot-starter/3.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;
}
}