【动态路由】系统web url整合系列【springcloud-gateway实现】【不改hosts文件版】组件一:多个Eureka路由过滤器

需求

实现URL web资源整合,实现使用一个web地址访问多个web资源

方案

本方案使用SpringCloud Gateway实现,不需要在hosts文件加添加域名映射(也不需要定义一系列域名),通过url路径来将请求转发到不同的Web资源

如:http://${proxyIP}:${proxyPORT}/nacos1

转发到

http://${nacos1IP}:${nacos1PORT}

1、优点

  • 不需要用户端修改hosts文件(或是域名服务支持)

2、缺点

  • session信息可能会生产覆盖,存在session失效和横向越权风险

小知识:

浏览器的session是按ip+端口(或域名+端口)作为唯一key存的

环境

spring-cloud 2024.0.0

spring-cloud gateway 4.2.0

实现代码

pom

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!-- 3.4.0  3.3.6  3.1.5  3.0.12  -->
        <version>3.4.0</version>
        <!--  lookup parent from repository  -->
        <relativePath/>
    </parent>

    <groupId>person.daizd</groupId>
    <artifactId>gwproxy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 统一管理jar包版本 -->
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>

        <!--   3.4.0   3.3.6  -->
        <spring-boot.version>3.4.0</spring-boot.version>
        <!-- 2024.0.0   2023.0.4    -->
        <spring-cloud.version>2024.0.0</spring-cloud.version>
        <!--  2023.0.3.2   2023.0.1.3    2022.0.0.0    2021.1   2021.0.6.1   2021.0.5.0   -->
        <spring-cloud-alibaba.version>2023.0.3.2</spring-cloud-alibaba.version>

        <!--  springcloud 2020.02 以后需要单独引入     4.1.4   4.0.5  3.1.9  3.0.6  -->
        <spring-cloud-starter-bootstrap.version>4.1.4</spring-cloud-starter-bootstrap.version>
        <!-- 0.3.0-RC 适配springboot3    0.2.12 适配springboot2    0.1.10 适配springboot1   -->
        <nacos-config-spring-boot-starter.version>0.3.0-RC</nacos-config-spring-boot-starter.version>

        <!--        <junit.version>5.8.2</junit.version>-->
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.18.28</lombok.version>

        <commons-lang3.version>3.14.0</commons-lang3.version>
        <io.micrometer.version>1.9.17</io.micrometer.version>

        <!--  pmd-core 7.0.0:  3.25.0  3.24.0  3.23.0  3.22.0
              pmd-core 6.55.0:  3.21.2    3.13.0  3.11.0 copote  -->
        <maven-pmd-plugin.version>3.26.0</maven-pmd-plugin.version>
        <maven-clean-plugin.version>3.26.0</maven-clean-plugin.version>
        <fastjson2.version>2.0.53</fastjson2.version>
    </properties>

    <dependencies>
        <!--gateway的依赖 springcloud开发-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>${spring-cloud-starter-bootstrap.version}</version>
        </dependency>

        <!--   之前工程即没有引入 SpringCloud,也没有引入 SpringCloudAlibaba  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-resolver-dns-native-macos</artifactId>
            <version>4.1.115.Final</version>
            <classifier>osx-aarch_64</classifier>
        </dependency>


        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.22</version>
<!--            <scope>test</scope>-->
        </dependency>

        <!-- 测试相关 这个包含junit5  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>

            <dependency>
                <groupId>io.micrometer</groupId>
                <artifactId>micrometer-registry-prometheus</artifactId>
                <version>${io.micrometer.version}</version>
                <scope>compile</scope>
            </dependency>

        </dependencies>

    </dependencyManagement>


    <build>

        <plugins>
            <!-- 用于支持热部署 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <!-- 表示它将创建(fork)一个新的JVM来运行编译器。 spring boot plugin 3.0.0没有这个属性了
                    <fork>true</fork> -->
                    <addResources>true</addResources>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.3.1</version>

                <executions>
                    <execution>
                        <id>copy-resource</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${basedir}/target</outputDirectory>
                            <resources>
                                <resource>
                                    <!-- 文件地址 -->
                                    <directory>${basedir}/src/main/resources</directory>
                                    <includes>
                                        <include>config.ini</include>
                                    </includes>
                                </resource>
                                <resource>
                                    <!-- 文件地址 -->
                                    <directory>${basedir}/src/main/resources</directory>
                                    <includes>
                                        <include>cookie.png</include>
                                    </includes>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <target>${maven.compiler.target}</target>
                    <source>${maven.compiler.source}</source>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.3.1</version>
                <executions>
                    <execution>
                        <phase>verify</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
            <!--  PMD toolkit  -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>3.26.0</version>
            </plugin>

            <!--- 支持单元测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.2</version>
            </plugin>
            <!--- 代码覆盖率工具 -->
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.12</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.7.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>person.daizd.GatewayApplication</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

配置

这里有很多组件:eureka、apollo、nacos、skywalking等,且每一类组件都有多套(如:有的各省一套)

CoffeeScript 复制代码
# id与uri的映射
gwproxy.routes.cteureka_local=http://eureka:eureka@127.0.0.1:8200
gwproxy.routes.cmdb_local=http://127.0.0.1:8071

# eureka
gwproxy.routes.cteureka_sh_test=http://eureka:eureka@10.130.***.157:8200
gwproxy.routes.sxeureka_sh_test=http://eureka:eureka@10.130.***.157:8206
gwproxy.routes.fjeureka_sh_test=http://eureka:eureka@10.130.***.157:8207

gwproxy.routes.zjeureka_gcgz_nmb3_prod=http://10.141.***.240:20222

# apollo
gwproxy.routes.apollo_sh_test=http://10.130.***.157:28003
gwproxy.routes.apollo_sh_sit=http://10.130.***.112:30001

gwproxy.routes.ctapollo__nmb3_prod=http://10.141.***.240:20400
gwproxy.routes.lnapollo_idc01_nmb3_prod=http://10.141.***.240:20403

# sentinel
gwproxy.routes.lnsentinel_idc01_nmb3_prod=http://10.141.***.240:23903
gwproxy.routes.jssentinel_idc01_nmb3_prod=http://10.141.***.240:23915
gwproxy.routes.ahsentinel_idc01_nmb3_prod=http://10.141.***.240:23923

# apisix
# http://${gwproxy.root_uri}/apisix/index.html?app=apisix_sh_test
gwproxy.routes.apisix_sh_test=http://10.130.***.157:9000

gwproxy.routes.ctapisix_gz_gzb1_prod=http://10.***.5.131:32065

gwproxy.routes.ctapisix_idc01_nmb3_prod=http://10.141.***.240:31600
gwproxy.routes.lnapisix_idc01_nmb3_prod=http://10.141.***.240:31603


# prometheus 
# http://${gwproxy.root_uri}/prometheus_sh_test/graph
gwproxy.routes.prometheus_sh_test=http://10.130.***.136:9090
gwproxy.routes.prometheus_idc01_gzb1c1_prod=http://10.***.32.245:30006
gwproxy.routes.prometheus_idc01_gzb1c2_prod=http://10.***.32.246:30006
gwproxy.routes.prometheus_idc01_gzb1c3_prod=http://10.***.32.247:30006

gwproxy.routes.prometheus_idc01_nmb3c1_prod=http://10.141.***.240:30006
gwproxy.routes.prometheus_idc01_nmb3c2_prod=http://10.141.***.241:30006
gwproxy.routes.prometheus_idc01_nmb3c3_prod=http://10.141.***.242:30006

gwproxy.routes.prometheus_sh_crm120=http://10.130.***.120:9090
gwproxy.routes.prometheus_sh_crm121=http://10.130.***.121:9090
gwproxy.routes.prometheus_gz_saas190=http://10.***.31.190:30900

# nacos
# http://${gwproxy.root_uri}/nacos_sh_test/nacos/#/login
gwproxy.routes.nacos_sh_test=http://10.130.***.92:18848

# gateway
gwproxy.routes.ahgateway_idc01_nmb3_prod=http://10.141.***.242:23266
gwproxy.routes.jsgateway_idc01_nmb3_prod=http://10.141.***.216:32015
gwproxy.routes.bjgateway_idc01_nmb3_prod=http://10.141.***.209:32008
gwproxy.routes.lngateway_idc01_nmb3_prod=http://10.141.***.204:30003

# elasticsearch
gwproxy.routes.elasticsearch_gz_247=http://10.***.6.247:9200

# kibana     待优化KibanaRoute1 用于同时支持kibana 直接访问和代理访问,不过官方说只能2选一,优化难度大
# /kibana_local/login?next=/kibana_local/
gwproxy.routes.kibana_local=http://esserver:5601

# /kibana_sh_test/login?next=/kibana_sh_test/
gwproxy.routes.kibana_sh_cs134_test=http://10.130.***.134:15601
gwproxy.routes.kibana_sh_jg92_test=http://10.130.***.92:5602

# /gzkibana_5601/login?next=/gzkibana_5601/
# /gzkibana_5601/login?msg=LOGGED_OUT
gwproxy.routes.kibana_gz_5601_prod=http://10.***.6.247:5601
gwproxy.routes.kibana_gz_5602_prod=http://10.***.6.247:5602
gwproxy.routes.kibana_gz_5603_prod=http://10.***.6.247:5603

# /nmkibana_5601/login?next=%2F
gwproxy.routes.kibana_nm_5601_prod=http://10.141.137.***:5601
gwproxy.routes.kibana_nm_5602_prod=http://10.141.137.***:5601
gwproxy.routes.kibana_nm_5603_prod=http://10.141.137.***:5601

# timetask  当前可参考kibana 通过改根路径解决(这里直接改应用包名)
# http://${gwproxy.root_uri}/timetask/toLogin
# http://${gwproxy.root_uri}/cttimetask_idc01_nmb3_prod/timetask/toLogin
# 因静态资源命名冲突,所以两种(改工程名与直接支持)配置不能同时存在
#
gwproxy.routes.timetask=http://10.141.***.240:22400
#gwproxy.routes.timetask-xxl-job=http://10.141.***.240:22400
gwproxy.routes.cttimetask_idc01_nmb3_prod=http://10.141.***.240:22400
gwproxy.routes.cttimetask_idc01_gzb1_prod=http://10.***.5.131:22400

# Grafana
# http://${gwproxy.root_uri}/grafana_sh_prod/login
gwproxy.routes.grafana_sh_prod=http://10.130.***.121:50001

# Ambari (Hadoop - Hbase )   
# http://${gwproxy.root_uri}/ambari_sh_prod/#/login
gwproxy.routes.ambari_sh_prod=http://10.***.6.231:8080

# kafka-map   
gwproxy.routes.kafkamap_sh_jg92_test=http://10.130.***.92:8083
gwproxy.routes.kafkamap_sh_prod=http://10.130.***.121:8093

# cmak
# /cmak_sh_jg93_test
gwproxy.routes.cmak_sh_jg93_test=http://10.130.***.93:8094
# 没启或没安装
gwproxy.routes.cmak_sh_prod=http://10.130.***.121:8094

# skywalking
gwproxy.routes.skywalking_sh_jg131_test=http://10.130.***.131:30198
# 
gwproxy.routes.skywalking_xw_nm_prod=http://10.141.***.41:31051
gwproxy.routes.skywalking_xw_gz_prod=hhttp://10.***.6.204:31051
gwproxy.routes.skywalking_idc01_nmb3_prod=hhttp://10.141.***.240:31051

# pinpoint
gwproxy.routes.pinpoint_idc01_gzb1_prod=http://10.***.5.131:20900
gwproxy.routes.pinpoint_idc01_nmb3_prod=http://10.141.***.95:20900

# redmine
gwproxy.routes.redmine_idc01_nmb3_prod=http://10.142.***.247:8080

# harbor
gwproxy.routes.harbor_idc01_gzb1_prod=http://10.***.100.18:8021

# jenkins
gwproxy.routes.jenkins_sh_cs124_prod=http://10.130.***.124:1906

# nexus
gwproxy.routes.nexus_sh_jg122_prod=http://10.130.***.122:18081

# gitlab
gwproxy.routes.gitlab_sh_jg122_prod=http://10.130.***.122:8082


# kubesphere
gwproxy.routes.kubesphere_sh_cs131_prod=http://10.130.***.131:30880


# nightingale 
# nightingale_sh_test/login   
# nightingale_sh_test/metric/explorer
# 10.130.***.120 nightingale.sh.test
# 10.130.***.120 nightingale.sh.prod
gwproxy.routes.nightingale_sh_test=http://10.130.***.136:17000
gwproxy.routes.nightingale_sh_prod=http://10.130.***.121:17000

# ~~~~~~~~~~~~~~~~   业务应用 begin   ~~~~~~~~~~~~~~~~~~~
# http://${gwproxy.root_uri}/portal1_sh_prod/login
gwproxy.routes.portal1_sh_prod=http://10.130.***.121:8082


gwproxy.routes.web2_sh_test=http://10.130.***.124:8201
# http://${gwproxy.root_uri}/web2_idc01_nmb3_prod/web2/
gwproxy.routes.web2_idc01_nmb3_prod=http://10.***.228.140:32100

配置类

XML 复制代码
/**
 * @Description: 从nacos获取配置
 * @Author: dand
 * @CreateDate: 2024/12/14 5:43 PM
 * @Version: 1.0
 */

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@ConfigurationProperties(prefix = "gwproxy")
@Getter
@Setter
@RefreshScope
public class NacosMappingConfig {

    private Map<String, String> routes;

}

动态路由实现【重点】

这里涉及很多组件,本文先上eureka的代码(其他组件支持代码读者可自行注释掉),后续更新文章其他组件相关动态路由实现代码会陆续上架

java 复制代码
package person.daizd.config;

import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import person.daizd.config.route.*;
import person.daizd.filters.factory.*;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/***
 * @Author dand   QQ:413881461
 * @Slogan 致敬大师,致敬未来的你
 */
@Slf4j
@Configuration
public class GatewayConfig {

    @Value("${gwproxy.root_uri}")
    private String rootUri;

    @Autowired
    private NacosMappingConfig nacosMappingConfig;

    @Autowired
    private CmdbFilterFactory1 cmdbFilterFactory1;
    @Autowired
    private CmdbFilterFactory2 cmdbFilterFactory2;

    @Autowired
    private ApisixFilterFactory1 apisixFilterFactory1;
    @Autowired
    private ApisixFilterFactory2 apisixFilterFactory2;

    @Autowired
    private EurekaFilterFactory1 eurekaFilterFactory1;
    @Autowired
    private EurekaFilterFactory2 eurekaFilterFactory2;

    @Autowired
    private N9eFilterFactory1 n9eFilterFactory1;
    @Autowired
    private N9eFilterFactory2 n9eFilterFactory2;

//    @Autowired
//    private KibanaFilterFactory1 kibanaFilterFactory1;
//    @Autowired
//    private KibanaFilterFactory2 kibanaFilterFactory2;

    @Autowired
    private TimeTaskFilterFactory1 timeTaskFilterFactory1;
    @Autowired
    private TimeTaskFilterFactory2 timeTaskFilterFactory2;

    @Bean
//    @RefreshScope
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) throws NacosException {

        log.debug("nacosMappingConfig:{}",nacosMappingConfig.getRoutes());

        // 需要换成配置中取
//        Map<String,String> map = new HashMap<String,String>();
        Map<String,String> map = nacosMappingConfig.getRoutes();
//        map.put("cteureka_local","http://eureka:eureka@127.0.0.1:8200");
//        map.put("cmdb_local","http://127.0.0.1:8071");

        RouteLocatorBuilder.Builder innerBuilder = builder.routes();

        Set<Map.Entry<String, String>> set = map.entrySet();
        Iterator<Map.Entry<String, String>> it = set.iterator();

        /**
         *  特别注意:
         *  1、路径名称不能与已有资源名重命
         *  2、相互之间不能是包含(子集)关系
         *
         *
         */
        // 先统一处理所有eureka
        while (it.hasNext()) {
            Map.Entry<String, String> entry = it.next();

            if(entry.getKey().contains("eureka")){
                innerBuilder = EurekaRoute.route(eurekaFilterFactory1, eurekaFilterFactory2,
                          innerBuilder,    entry,  rootUri);
            }
            // 防止不重复处理 用 else if
            else if(entry.getKey().contains("cmdb")){
                innerBuilder = CmdbRoute.route(cmdbFilterFactory1, cmdbFilterFactory2,
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("sentinel")){
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("apisix")){
                innerBuilder = ApisixRoute2.route(apisixFilterFactory1,apisixFilterFactory2,
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("ddal")){
                innerBuilder = CmdbRoute.route(cmdbFilterFactory1, cmdbFilterFactory2,
                        innerBuilder,    entry,  rootUri);
            }
            /* 待优化KibanaRoute1 用于同时支持kibana 直接访问和代理访问,不过官方说只能2选一,优化难度大
               另外也可开通SSO能力后研究其他方案
               前端使用了react,采用nodejs部署
             */
            else if(entry.getKey().contains("kibana")){
                innerBuilder = KibanaRoute2.route(
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("elasticsearch")){
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("nacos")){
                innerBuilder = CmdbRoute.route(cmdbFilterFactory1, cmdbFilterFactory2,
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("prometheus")){
                innerBuilder = CmdbRoute.route( cmdbFilterFactory1, cmdbFilterFactory2,
                        innerBuilder,    entry,  rootUri);
            }
            // @todo  n9e 前端使用了React和D3.js, 等官方支持修改根路径(改为与配置名一致)或 二次n9e前端工程(https://github.com/n9e/fe)
            else if(entry.getKey().contains("nightingale")){
                innerBuilder = N9eRoute.route(    n9eFilterFactory1, n9eFilterFactory2,
                        innerBuilder,    entry,  rootUri);
            }
            /*
             * 方案一: 改timetask 上下文(应用名/根路径)-- 推荐(kibana 方案)
             * 方案二: 直接支持(不调整应用) -- 目前与n9e一样存在类似(菜单路径)问题
             */
            else if(entry.getKey().contains("timetask")){
                innerBuilder = KibanaRoute2.route(
                        innerBuilder,    entry,  rootUri);

//                innerBuilder = TimeTaskRoute.route( timeTaskFilterFactory1, timeTaskFilterFactory2,
//                        innerBuilder,    entry,  rootUri);
            }
            // @todo
            else if(entry.getKey().contains("grafana")){  // /grafana_sh_prod/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            else if(entry.getKey().contains("ambari")){  // /ambari_sh_prod/#/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // eureka √
            else if(entry.getKey().contains("kafkamap")){  // /kafkamap_sh_jg92_test/#/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // cmak 存在类似n9e的菜单路径问题
            else if(entry.getKey().contains("cmak")){  // /cmak_sh_jg93_test
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 补充了一个js待验证
            else if(entry.getKey().contains("skywalking")){  // /skywalking_sh_jg131_test/General-Service/Services
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 初步让为 √
            else if(entry.getKey().contains("pinpoint")){  // /pinpoint_gcgz_gzb1_prod/#/main
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 120网络不通
            else if(entry.getKey().contains("redmine")){  // /redmine_gcgz_nmb3_prod/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 初步让为harbor上下文路径可以修改
            else if(entry.getKey().contains("harbor")){  // /harbor_gcgz_gzb1_prod/harbor/sign-in
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 能登陆,但主页面显示不正常
            else if(entry.getKey().contains("jenkins")){  // /jenkins_sh_cs124_prod/jenkins/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 登陆有问题
            else if(entry.getKey().contains("nexus")){  // /nexus_sh_jg122_prod/nexus/#welcome
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 静态资源访问有问题
            else if(entry.getKey().contains("gitlab")){  // /gitlab_sh_jg122_prod/users/sign_in
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 未知错误
            else if(entry.getKey().contains("kubesphere")){  // /kubesphere_sh_cs131_prod/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
            // 可以登陆,静态资源加载有问题
            else if(entry.getKey().contains("opsportal")){  // /opsportal_sh_prod/login
                innerBuilder = CommonRoute.route(
                        innerBuilder,    entry,  rootUri);
            }
        };

        return innerBuilder.build();
    }

}

eureka动态路由实现类

java 复制代码
package person.daizd.config.route;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.route.builder.UriSpec;
import person.daizd.filters.factory.*;

import java.util.Map;
import java.util.function.Function;

/**
 * 路由
 *
 * @Author: daizd
 * @CreateDate: 2024/12/13 9:28 PM
 * @Version: 1.0
 */
@Slf4j
public class EurekaRoute {
    public static RouteLocatorBuilder.Builder route(EurekaFilterFactory1 filterFactory1,
                                                    EurekaFilterFactory2 filterFactory2,
                                                    RouteLocatorBuilder.Builder innerBuilder,
                                                    Map.Entry<String, String> entry,
                                                    String rootUri
    ){
        // 每个eureka要配两个路由, 第一个
        innerBuilder = innerBuilder.route( entry.getKey()+"_1", // id
                r -> r.path("/"+entry.getKey()+"/**")       //   --   /cteureka/**
                        .filters( f -> f.stripPrefix(1)
                                .preserveHostHeader()
                                .saveSession()  )

                        .uri( entry.getValue() ));


        log.debug("Referer:"+rootUri+"/"+entry.getKey()+"[^\\s]*" );
        log.debug("uri:"+entry.getValue() );
        // 每个eureka要配两个路由,第二个  modifyRedirectFilterFactory
        innerBuilder.route( entry.getKey()+"_2", // id
                r -> r.header("Referer",rootUri+"/"+entry.getKey()+"[^\\s]*" )
//                        .filters( f -> f.stripPrefix(0).preserveHostHeader() )
                        .filters(new Function<GatewayFilterSpec, UriSpec>() {
                            @Override
                            public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) {
                                return gatewayFilterSpec.filter(
                                        filterFactory2.apply(applyFilter("app",entry.getKey() ))
                                );
                            }
                        })
                        .uri( entry.getValue() ));
        return innerBuilder;
    }

    /**
     * 构造config
     *
     * @param headerName
     * @param headerValue
     * @return NameValueConfig
     */
    private static AbstractNameValueGatewayFilterFactory.NameValueConfig applyFilter(String headerName, String headerValue) {
        AbstractNameValueGatewayFilterFactory.NameValueConfig config = new AbstractNameValueGatewayFilterFactory.NameValueConfig();
        config.setName(headerName);
        config.setValue(headerValue);
        return config;
    }
}

GatewayFilterFactory1【保留】

java 复制代码
package person.daizd.filters.factory;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * 用在路由1上
 *
 * @Author: dand
 * @CreateDate: 2024/12/8 7:49 PM
 * @Version: 1.0
 *
 *  没有用到
 */
@Component
@Slf4j
public class EurekaFilterFactory1 extends AbstractNameValueGatewayFilterFactory  {

    @Value("${gwproxy.root_uri}")
    private String rootUri;

    /**
     * 1、如果是重定向则定向到配置参数(应用名)对应的路径
     * 2、如果不是重定向,且第一级路径与应用名不一致,则修改为一致
     *
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply( NameValueConfig config) {
        return (exchange, chain) ->
                chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    ServerHttpRequest request = exchange.getRequest();
                    ServerHttpResponse response = exchange.getResponse();
                    HttpStatusCode statusCode = response.getStatusCode();

                    String name = config.getName();
                    String value = config.getValue();
                    String rootUrl = rootUri+"/"+config.getValue();
                    if (statusCode == HttpStatus.SEE_OTHER || statusCode == HttpStatus.FOUND) {
                        String location = response.getHeaders().getFirst(HttpHeaders.LOCATION);
                        // 修改location的逻辑
                        if(response.getHeaders().containsKey("Set-Cookie")
//                                || request.getHeaders().containsKey("Cookie")
                        ){
                            // 有 Set-Cookie  和 cookie都认为已登陆
                            response.getHeaders().set(HttpHeaders.LOCATION, rootUrl );
                            log.info("response.getHeaders().containsKey(\"Set-Cookie\") , to rootUrl:{} ",rootUrl);
                        }else{
                            response.getHeaders().set(HttpHeaders.LOCATION, rootUrl+"/signin" );
                            log.info(" else : {}/signin ",rootUrl);
                        }
                    }
                }));
    }

    @Override
    public Class<NameValueConfig> getConfigClass() {
        // 如果你有配置属性,返回配置属性的类;如果没有,返回null
        return NameValueConfig.class; // 或者 return YourConfigProperties.class;
    }
}

GatewayFilterFactory2

java 复制代码
package person.daizd.filters.factory;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;

/**
 * 用在路由2上
 *
 * @Author: dand
 * @CreateDate: 2024/12/8 7:49 PM
 * @Version: 1.0
 */
@Component
@Slf4j
public class EurekaFilterFactory2 extends AbstractNameValueGatewayFilterFactory  {

    @Value("${gwproxy.root_uri}")
    private String rootUri;

    /**
     * 1、如果是重定向则定向到配置参数(应用名)对应的路径
     * 2、如果不是重定向,且第一级路径与应用名不一致,则修改为一致
     *
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply( NameValueConfig config) {
        return (exchange, chain) ->
                chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    ServerHttpResponse response = exchange.getResponse();
                    HttpStatusCode statusCode = response.getStatusCode();

                    String remoteIp = exchange.getRequest().getRemoteAddress().getHostName();
                    log.info("remoteIp:"+remoteIp);

                    // "http://127.0.0.1:8089/oldcmdb";
                    String rootUrl = rootUri+"/"+config.getValue();
                    System.out.println("statusCode:"+statusCode);
//                    FluxOnAssembly a;
                    if (statusCode != HttpStatus.SEE_OTHER && statusCode != HttpStatus.FOUND) {

                        ServerHttpRequest request = exchange.getRequest();
                        URI uri = request.getURI() ;
                        String path = request.getPath().value();

                        HttpHeaders headers = request.getHeaders();
                        List<String> referer = headers.get("referer");
                        log.info("path:{}", uri.getPath() );
                        if( referer!=null
                                && referer.size()>0
                                && referer.get(0).indexOf( rootUrl ) != -1 // referer 包含:http://127.0.0.1:8089/oldcmdb
                                && uri.getPath().indexOf( "/"+config.getValue() ) == -1  // uri 不包含 /oldcmdb
                                && (uri.getPath().equalsIgnoreCase("/")
                                   || uri.getPath().equalsIgnoreCase("/lastn")   )
                                  ) //  是html页面
                        {
                            // 重定向到 RewritePath=/(?<segment>.*), /oldcmdb/$\{segment}
                            exchange.getResponse().setStatusCode(HttpStatus.FOUND);
                            exchange.getResponse().getHeaders().setLocation( URI.create( "/"+config.getValue()+ path) );
                            log.info("uri.getPath().indexOf( \".html\") ");
                        }
                    }
                }));
    }

    @Override
    public Class<NameValueConfig> getConfigClass() {
        // 如果你有配置属性,返回配置属性的类;如果没有,返回null
        return NameValueConfig.class; // 或者 return YourConfigProperties.class;
    }
}

效果说明

为了让读者更好的读懂上面的代码,下面给出静态配置实现(上面代码动态实现了该效果且更灵活)

XML 复制代码
server:
  port: 8089

gwproxy:
  root_uri: ${root_uri:http://127.0.0.1:8089}

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      httpclient:
        response-timeout: PT60S
        connect-timeout: 2000
        # 链接池配置
        pool:
          # 最大连接数
          max-connections: 10000
          # 仅对于FIXED类型,等待获取线程的最长时间(毫秒)
          acquire-timeout: 1000
          # 最大空闲时间
          max-idle-time: PT10S
          # 设置固定链接池
          type: fixed
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true


      #路由规则
      routes:

        #  ~~~~~~~~  eureka  ~~~~~~~~~~~~~~~~
        - id: cteureka_route_1 #  8100000    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://eureka:eureka@127.0.0.1:8200          #匹配后提供服务的路由地址
          predicates:
          - Path=/cteureka/**         # 断言,路径相匹配的进行路由
          filters:
          - StripPrefix=1  # 转发之前去掉第一层路径

        - id: cteureka_route_2
          uri: http://eureka:eureka@127.0.0.1:8200          #匹配后提供服务的路由地址
          predicates:
            - Header=Referer,${gwproxy.root_uri}/cteureka


management:
  info: # 显示任意的应用信息,默认关闭  springBoot版本:2.7.15  GA如果是<2.7.5 的版本默认是开启的
    env:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"
      base-path: /whoami
info:
  app:
    name: gateway
    encoding: "@project.build.sourceEncoding@"
    java:
      source: "@java.version@"
      target: "@java.version@"
  company:
    name: www.asiainfo.com

debug: true

附件一:整合界面地址效果截图

附件二:springcloud gateway官方文档

Spring Cloud Gateway 帮助文档

Spring Cloud Gateway 官方首页

相关推荐
研究司马懿14 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
Java后端的Ai之路1 天前
【Spring全家桶】-一文弄懂Spring Cloud Gateway
java·后端·spring cloud·gateway
研究司马懿6 天前
【云原生】Gateway API介绍
云原生·gateway
研究司马懿6 天前
【云原生】Gateway API路由、重定向、修饰符等关键操作
云原生·gateway
研究司马懿6 天前
【云原生】初识Gateway API
云原生·gateway
七夜zippoe7 天前
API网关设计模式实战 Spring Cloud Gateway路由过滤限流深度解析
java·设计模式·gateway·路由·api网关
汪碧康7 天前
一文讲解kubernetes的gateway Api的功能、架构、部署、管理及使用
云原生·容器·架构·kubernetes·gateway·kubelet·xkube
大佐不会说日语~7 天前
Docker Compose 部署 Spring Boot 应用 502 Bad Gateway 问题排查与解决
spring boot·docker·gateway·maven·故障排查
Dontla9 天前
Kubernetes流量管理双雄:Ingress与Gateway API解析(Nginx与Ingress与Gateway API的关系)
nginx·kubernetes·gateway
JavaLearnerZGQ9 天前
Gateway网关将登录用户信息传递给下游微服务(完整实现方案)
微服务·架构·gateway