wmSafe致力于互联网资源
道可道,非恒道,名可名,非恒名

【Java】记录一次SpringCloud Gateway3.1.6/Springboot2.x集成Knife4j + swagger2 教程

本文最后更新于2023-10-12,已经有406 天没有更新,如果文章内容或图片资源失效,请留言反馈,我们会及时处理,谢谢!

前言

这个SpringCloud Gateway集成Knife4j + swagger2也是有挺多坑的,如果不研究很容易掉坑里,这里给大家减少点坑,并快速集成好,直接用。Knife4j + swagger2是一个美观的api文档用于给前端等等对接api用的,也方便自己测试接口

我这里用到的Gateway版本为3.1.6,是属于SpringCloud Alibaba2021.0.4.0版本下的。用到的Knife4j为官方默认的4.0.0
【Java】记录一次SpringCloud Gateway3.1.6/Springboot2.x集成Knife4j + swagger2 教程

这是我的项目结构目录:
【Java】记录一次SpringCloud Gateway3.1.6/Springboot2.x集成Knife4j + swagger2 教程
其中最需要关注的就是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());
    }

}

在启动类上这样配置就可以在服务启动的时候自动输出api文档地址了
【Java】记录一次SpringCloud Gateway3.1.6/Springboot2.x集成Knife4j + swagger2 教程

本原创文章未经允许不得转载 | 若要转载请注明出处,否则将承担相应的法律责任!
本文链接: https://blog.wm404.com/2023/09/06/48ca65d5.html
赞赏排名 赞赏支持

评论

  • captcha
暂无评论,要不来一发?

您的关注就是我们最大的支持

联系我们 关于我们