前言
这个SpringCloud Gateway集成Knife4j + swagger2也是有挺多坑的,如果不研究很容易掉坑里,这里给大家减少点坑,并快速集成好,直接用。Knife4j + swagger2是一个美观的api文档用于给前端等等对接api用的,也方便自己测试接口
我这里用到的Gateway版本为3.1.6,是属于SpringCloud Alibaba2021.0.4.0版本下的。用到的Knife4j为官方默认的4.0.0
这是我的项目结构目录:
其中最需要关注的就是SwaggerHandler.java、SwaggerResourceConfig.java、Knife4jConfiguration.java还有网关的bootstrap.yml
步骤一:引用Knife4j POM
<!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
步骤二:配置网关swagger发现
在网关微服务里新建软件包config,在包内新建类:SwaggerHandler.java、SwaggerResourceConfig.java
SwaggerHandler.java代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
import java.util.Optional;
/*
处理与Swagger相关的请求,并提供Swagger的安全配置、UI配置和资源
*/
@RestController
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration; // 安全配置
@Autowired(required = false)
private UiConfiguration uiConfiguration; // UI配置
private final SwaggerResourcesProvider swaggerResources; // Swagger资源提供者
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
// 获取Swagger安全配置
@GetMapping("/swagger-resources/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
// 获取Swagger UI配置
@GetMapping("/swagger-resources/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
// 获取Swagger资源
@GetMapping("/swagger-resources")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
SwaggerResourceConfig.java代码如下:
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@Slf4j // 使用Lombok生成日志对象
@Component // 将该类标记为Spring Bean组件
@Primary // 在多个相同类型的Bean存在时,优先选择该实现
@AllArgsConstructor // 自动生成带有所有参数的构造函数
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
private final RouteLocator routeLocator; // 注入RouteLocator对象,用于获取路由信息
private final GatewayProperties gatewayProperties; // 注入GatewayProperties对象,用于获取网关配置信息
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>(); // 创建Swagger资源列表
List<String> routes = new ArrayList<>(); // 创建路由列表
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); // 获取所有路由的ID,并添加到路由列表中
// 遍历网关配置的路由信息
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName())) // 筛选出路径谓词
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs")))); // 添加Swagger资源到列表中
});
return resources; // 返回Swagger资源列表
}
private SwaggerResource swaggerResource(String name, String location) {
log.info("name:{},location:{}",name,location); // 日志记录名称和位置信息
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name); // 设置Swagger资源的名称
swaggerResource.setLocation(location); // 设置Swagger资源的访问路径
swaggerResource.setSwaggerVersion("2.0"); // 设置Swagger版本号为2.0
return swaggerResource; // 返回Swagger资源对象
}
}
步骤三:配置网关yml
以下是我网关的bootstrap.yml(也就是application.yml不过我用了spring-cloud-starter-bootstrap)
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: cloud-unifiedUser-service
uri: lb://cloud-unifiedUser-service
predicates:
- Path=/unifiedUser/**
filters:
- RewritePath=/unifiedUser/v2/api-docs,/v2/api-docs
- id: cloud-auth-service
uri: lb://cloud-auth-service
predicates:
- Path=/auth/**
#数据源
datasource:
url: jdbc:mysql://127.0.0.1:3306/ygxc_cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: ygxc_cloud
password: nyrfJ8AkbsJMfR8Z
redis:
# Redis服务器地址
host: 127.0.0.1
# Redis服务器端口号
port: 6379
# 使用的数据库索引,默认是0
database: 6
# 连接超时时间
timeout: 30000
# 设置密码
password: "123456"
lettuce:
pool:
# 最大阻塞等待时间,负数表示没有限制
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 5
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中最大连接数,负数表示没有限制
max-active: 20
# 分布式事务配置
seata:
enabled: false
# 白名单接口不需要鉴权
white-list: /order/excel/*,/payment/excel/*
其中最需要注意的就是gateway下的routes配置,其中最重要的就是RewritePath=/unifiedUser/v2/api-docs,/v2/api-docs,因为网关统一转发请求是需要前缀的,比如我在这个cloud-unifiedUser-service微服务的controller里配置的统一前缀是unifiedUser,而当网关集成Knife4j的时候,http://网关地址:网关端口/doc.html 里面访问其他微服务的/v2/api-docs其实是不带前缀的!所以如果没有配置这个网关过滤规则,那么请求会404。
我配置的过滤规则无非就是判断url如果是/unifiedUser/v2/api-docs,直接转发/v2/api-docs不要加前缀,这样确保网关访问微服务swagger不会404
步骤四:配置微服务的swagger config
在其他微服务下创建软件包config,软件包内新建Knife4jConfiguration.java类
代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
@Bean(value = "dockerBean")
public Docket dockerBean() {
//指定使用Swagger2规范
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
//描述字段支持Markdown语法
.description("# Knife4j RESTful APIs")
.termsOfServiceUrl("https://doc.xiaominfo.com/")
.version("1.0")
.build())
//分组名称
// .groupName("用户服务")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.wmsafe.unifiedUser.controller"))
.paths(PathSelectors.any())
.build();
}
}
其中最需要注意的就是groupName最好不要配置,因为配置了那么/v2/api-docs后面要跟groupName参数。例如:/v2/api-docs?groupName=测试分组,这样的话网关那边就要做适配非常麻烦,不建议加,没做适配的话加上会404错误
结尾
自此Gateway集成Knife4j + swagger2结束
随便附赠一个微服务启动的时候快捷输出swagger文档地址的功能
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@SpringBootApplication
@MapperScan("com.wmsafe.unifiedUser.mapper")
public class UnifiedUserServiceMain {
public static void main(String[] args) {
// SpringApplication.run(UnifiedUserServiceMain.class, args);
SpringApplication app = new SpringApplication(UnifiedUserServiceMain.class);
Environment env = app.run(args).getEnvironment();
app.setBannerMode(Banner.Mode.CONSOLE);
logApplicationStartup(env);
}
private static void logApplicationStartup(Environment env) {
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
String serverPort = env.getProperty("server.port");
String contextPath = env.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(contextPath)) {
contextPath = "/doc.html";
} else {
contextPath = contextPath + "/doc.html";
}
String hostAddress = "localhost";
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.warn("The host name could not be determined, using `localhost` as fallback");
}
log.info("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}{}\n\t" +
"External: \t{}://{}:{}{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
serverPort,
contextPath,
protocol,
hostAddress,
serverPort,
contextPath,
env.getActiveProfiles());
}
}