Knife4j介绍
- 官网:doc.xiaominfo.com/
- Knife4j是一个集Swagger2 和 OpenAPI3 为一体的增强解决方案
SpringCloud 集成 Knife4j
1、思路及分包情况展示
将基础配置抽取成一个通用的工具类,业务模块如需依赖该模块直接引入该工具包即可,然后进行简单的配置,此时就实现了单体SpringBoot集成Knife4j(如图:common-knife4j模块)
接下来将各业务模块访问通过网关路由+nacos相关规则代理出来,实现一个地址访问多个接口文档(如图:ability-gateway模块 knife4j包)
2、单体业务模块集成Knife4j
2.1 knife4j工具类抽取
引入相关依赖包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
添加 SwaggerProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* description: swagger基础信息配置
* author: chengpengai
* date: 2023-04-08 周六 10:08 PM
*/
@Component
@ConfigurationProperties("swagger.config")
public class SwaggerProperties {
//版本
private String docVersion;
//标题
private String title;
//描述
private String description;
//联系人姓名
private String contactName;
//访问路径
private String contactUrl;
//联系人邮箱
private String contactEmail;
//许可证
private String license;
//许可证地址
private String licenseUrl;
public SwaggerProperties() {
}
public String getDocVersion() {
return this.docVersion;
}
public void setDocVersion(String docVersion) {
this.docVersion = docVersion;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getContactName() {
return this.contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getContactUrl() {
return this.contactUrl;
}
public void setContactUrl(String contactUrl) {
this.contactUrl = contactUrl;
}
public String getContactEmail() {
return this.contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public String getLicense() {
return this.license;
}
public void setLicense(String license) {
this.license = license;
}
public String getLicenseUrl() {
return this.licenseUrl;
}
public void setLicenseUrl(String licenseUrl) {
this.licenseUrl = licenseUrl;
}
}
添加 Knife4jConfig.java
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import javax.annotation.Resource;
import java.util.List;
/**
* description:
* 配置knife4j主页信息
* 需要 knife4j 的权限控制(账号密码) 才需要引用此包: @Import(BeanValidatorPluginsConfiguration.class), 对应jar:spring-boot-starter-validation
*
* author: chengpengai
* date: 2023-04-08 周六 10:08 PM
*/
@Configuration
@EnableSwagger2WebMvc
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Knife4jConfig {
@Resource
private SwaggerProperties swaggerProperties;
@Value("${spring.application.name}")
private String projectName;
@Bean
public Docket createRestApi() {
List<Parameter> parameters = Lists.newArrayList();
parameters.add((new ParameterBuilder()).name("token").description("认证token").modelRef(new ModelRef("string")).parameterType("header").required(false).build());
parameters.add((new ParameterBuilder()).name("token").description("认证token").modelRef(new ModelRef("string")).parameterType("header").required(false).build());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(this.apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
// .groupName(StringUtils.isEmpty(projectName)? UUID.randomUUID().toString(): projectName)
.globalOperationParameters(parameters)
.securitySchemes(this.securitySchemes())
.securityContexts(this.securityContexts());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(this.swaggerProperties.getTitle())
.description(this.swaggerProperties.getDescription())
.contact(new Contact(this.swaggerProperties.getContactName(), this.swaggerProperties.getContactUrl(), this.swaggerProperties.getContactEmail()))
.termsOfServiceUrl(this.swaggerProperties.getContactUrl())
// .license(this.swaggerProperties.getLicense())
// .licenseUrl(this.swaggerProperties.getLicenseUrl())
.version(this.swaggerProperties.getDocVersion())
.build();
}
private List<ApiKey> securitySchemes() {
return Lists.newArrayList(new ApiKey[]{new ApiKey("token", "token", "header")});
}
private List<SecurityContext> securityContexts() {
return Lists.newArrayList(new SecurityContext[]{SecurityContext.builder().securityReferences(this.defaultAuth()).forPaths(PathSelectors.regex("^(?!auth).*$")).build()});
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[]{authorizationScope};
return Lists.newArrayList(new SecurityReference[]{new SecurityReference("token", authorizationScopes)});
}
}
添加SwaggerEnable
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.lang.annotation.*;
/**
* description:
* author: chengpengai
* date: 2023-04-08 周六 10:08 PM
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableSwagger2WebMvc
@EnableKnife4j
public @interface SwaggerEnable {
}
添加基础配置(也可添加到业务模块中)
spring:
#开启资源映射
resources:
add-mappings: true
knife4j:
#是否启用Knife4j
enable: true
#访问文档认证设置
basic:
enable: true
username: acp
password: 888888
2.2 业务服务引入Knife4j
引入common-knife4j服务包(该包为上面抽取的服务,非三方包)
<dependency>
<groupId>org.ai.com</groupId>
<artifactId>common-knife4j</artifactId>
</dependency>
添加配置信息
swagger:
config:
docVersion: 1.0
title: 订单模块
description: 订单模块1.0
contactName: 艾(ChengPengAi)
contactUrl: www.chengpeng.com
contactEmail: 3164604364@qq.cpm
测试
访问地址:http://127.0.0.1:服务端口/项目访问前缀/doc.html#/home
3、网关代理统一访问处理
网关路由配置
spring:
cloud:
gateway:
routes:
- id: LearningBuddy (此处需要和业务模块 server.servlet.context-path 值相同)
uri: lb://learning-buddy-server (spring.application.name)
predicates:
- Path=/LearningBuddy/**
- id: oauth
uri: lb://ability-oauth
predicates:
- Path=/oauth/**
添加 Knife4jResourceProvider.java
import com.alibaba.cloud.nacos.ribbon.NacosRibbonClientConfiguration;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.cloud.nacos.ribbon.NacosServerList;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* description:
* 使用Spring Boot单体架构集成swagger时,是通过包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,单个服务类似于原来业务组;
* springfox-swagger提供的分组接口是swagger-resource,返回的是分组接口名称、地址等信息;
* author: chengpengai
* date: 2023-04-12 周三 2:03 PM
*/
@Component
@RequiredArgsConstructor
public class Knife4jResourceProvider implements SwaggerResourcesProvider {
/**
* swagger2默认的url后缀
*/
private static final String SWAGGER2_URL = "/v2/api-docs";
/**
* 路由定位器
*/
private final RouteLocator routeLocator;
/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String gatewayName;
/**
* nacos客户端
*/
@Autowired
private DiscoveryClient discoveryClient;
/**
* 获取 Swagger 资源
* 获取条件:所有配置的网关路由服务 - 排除网关地址 - 未注册到nacos的服务或者nacos存在异常的服务
*/
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routeHosts = new ArrayList<>();
// 1. 获取路由Uri 中的Host=> 服务注册则为服务名=》app-service001
routeLocator.getRoutes()
.filter(route -> route.getId() != null)
//过滤掉网关的文档信息
.filter(route -> !gatewayName.equals(route.getId()))
//根据服务状态注入api文档信息
.filter(route -> !CollectionUtils.isEmpty(discoveryClient.getInstances(route.getUri().getHost())))
.subscribe(route -> routeHosts.add(route.getId()));
// 2. 创建自定义资源
for (String routeHost : routeHosts) {
String serviceUrl = "/" + routeHost + SWAGGER2_URL; // 后台访问添加服务名前缀
SwaggerResource swaggerResource = new SwaggerResource(); // 创建Swagger 资源
swaggerResource.setUrl(serviceUrl); // 设置访问地址
swaggerResource.setName(routeHost); // 设置名称
swaggerResource.setSwaggerVersion("3.0.0");
resources.add(swaggerResource);
}
return resources;
}
}
添加 Knife4jResourceController.java
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.swagger.web.SwaggerResource;
import java.util.List;
/**
* description: knife4j访问资源接口重写
* author: chengpengai
* date: 2023-04-12 周三 2:06 PM
*/
@RestController
@RequestMapping("/swagger-resources")
@RequiredArgsConstructor
public class Knife4jResourceController {
private final Knife4jResourceProvider knife4jResourceProvider;
@RequestMapping
public ResponseEntity<List<SwaggerResource>> swaggerResources() {
return new ResponseEntity<>(knife4jResourceProvider.get(), HttpStatus.OK);
}
}
测试
通过左侧下拉可以动态切换