SpringCloud Gateway知识点整理和全局过滤器实现

news2025/5/13 22:43:15

predicate(断言):

判断uri是否符合规则 •  最常用的的就是PathPredicate,以下列子就是只有url中有user前缀的才能被gateway识别,否则它不会进行路由转发

 routes:
        - id: ***
          # uri: lb://starry-sky-upms
          uri: http://localhost:9003/
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix=1

filter(过滤器) :

上面的例子为例,StripPrefix就是说gateway进行转发的时候要去掉user前缀,所以在后端服务的api层,是不需要加user前缀的。

 •  全局过滤器 •  自定义全局过滤器,即实现GlobalFilter,就可以实现全局拦截,执行自己的业务逻辑。权限鉴定就是这一步做的。

 •  动态路由(要使新加入的route生效)

•  新增Listener,监听nacos配置改变(RefreshRoutesEvent),当发现route有增加的时候,就调用actuator的refresh方法,刷新到内存汇总即可。  

以上4个部分都可以自定义符合自己业务需求的扩展。 在实际业务中,可能用gateway来做登陆校验,以及访问权的判断,还可以配置白名单实现哪些路径可以忽略。

1.  登陆校验:不同平台的登陆校验,校验token是否有效

2.  访问权限校验:比如有没有功能权限码之类的,系统里面会为不同的人配置不同的功能权限

3.  配置忽略路径:有些路径可能不需要上述2步校验,所以直接可以通过  可以写自定义一个GlobalFilter,重写invoke方法。

1.  先处理ignore路径,用正则表达式匹配,如果符合匹配,则直接放行。

2. 如果不是白名单,则先进行登陆校验,再进行权限校验。

动态路由的实现:

private void initListener() {
        try {
            nacosConfigManager.getConfigService()
                    .addListener(dynamicRouteDataId, nacosGroup,
                            new AbstractListener() {

                                @Override
                                public void receiveConfigInfo(String configInfo) {
                                    log.info("Init NacosListener: data info{}" + configInfo);
                                    try {
                                        YAMLMapper mapper = new YAMLMapper();
                                        List<RouteDefinition> list = mapper.readValue(configInfo, new TypeReference<>() {});
                                        list.forEach(definition -> {
                                            Result result = dynamicRouteService.update(definition);
                                            log.info("update route,{},{}", definition.getId(), JsonUtils.toJson(result));
                                        });
                                    } catch (Exception e) {
                                        log.error("update route error:{}, configInfo:{}", e.getMessage(), e, configInfo);
                                        exceptionCounterHandler.increment();
                                    }
                                }
                            });
        } catch (Exception e) {
            log.error("Gateway addListener error:{}", e.getMessage(), e);
            exceptionCounterHandler.increment();
        }
    }

通过nacos 的NacosConfigManager获取NacosConfigService,通过addListener方法,其实是像nacos的WorkClient类里面添加一个监听器。WorkClient类是nacos管理配置的一个类,它里面启动了一个长连接定时任务(延迟队列)去不断的check配置的改变(checkConfigInfo),一旦有配置改变,就通知监听器

public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
        this.agent = agent;
        this.configFilterChainManager = configFilterChainManager;

        // Initialize the timeout parameter

        init(properties);
....

        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    checkConfigInfo();
                } catch (Throwable e) {
                    LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
                }
            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);
    }

继续跟下去,就可以看到LongPollingRunnable的run方法中,会比较MD5是否一致,不一致就会发消息

void checkListenerMd5() {
        for (ManagerListenerWrap wrap : listeners) {
            if (!md5.equals(wrap.lastCallMd5)) {
                safeNotifyListener(dataId, group, content, md5, wrap);
            }
        }
    }

在面试时可能会问,gateway的接入流程:

1. spring boot启动过程不用说了,接入gateway的starter jar包,然后自动装配各种gateway的bean 2. 服务启动成功后,输入url,gateway如何拦截请求? 首先Netty 的 HTTP 服务通过HttpServerInitializer启动,最终请求会传递到 ​Spring WebFlux 的DispatcherHandler:

@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		if (this.handlerMappings == null) {
			return createNotFoundError();
		}
		return Flux.fromIterable(this.handlerMappings)
                                  //mapping.getHandler 会调RoutePredicateHandlerMapping
				.concatMap(mapping -> mapping.getHandler(exchange))
				.next()
				.switchIfEmpty(createNotFoundError())
                                  // invokeHandler会去处理gateway的Filter
				.flatMap(handler -> invokeHandler(exchange, handler))
				.flatMap(result -> handleResult(exchange, result));

gateway注入的类入口是:RoutePredicateHandlerMapping,它实现了spring webflux的接口HandlerMapping,HandlerMapping 我们都很熟悉,spring mvc的常客,处理http请求的。所以spring在处理过程中就会调用这个类的方法:getHandlerInternal; FilteringWebHandler实现WebHandler,所以在上面Handler处理的时候就会找到它,调用它的handle方法

@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
		List<GatewayFilter> gatewayFilters = route.getFilters();

		List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
		combined.addAll(gatewayFilters);
		// TODO: needed or cached?
		AnnotationAwareOrderComparator.sort(combined);

		if (logger.isDebugEnabled()) {
			logger.debug("Sorted gatewayFilterFactories: " + combined);
		}

		return new DefaultGatewayFilterChain(combined).filter(exchange);
	}

项目里面如何用的gateway?如何用它做的权限校验?

  • 首先把gateway的starter引入,然后把gateway所需要个bean交给spring容器管理
  • request请求访问来了后,通过netty的webflux把请求转发给HandlerMapping,它在执行handle方法的时候,会去查所有实现GlobalFilter的类,并且调用其filter方法。还会去查所有的FilterFactory,并且调filter方法
	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
                //这里是获取所有***FilterFactory(比如:gateway自带的***FilterFactory)
		List<GatewayFilter> gatewayFilters = route.getFilters();
                //这里是globalFilters
		List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
		combined.addAll(gatewayFilters);
		// TODO: needed or cached?
		AnnotationAwareOrderComparator.sort(combined);

		if (logger.isDebugEnabled()) {
			logger.debug("Sorted gatewayFilterFactories: " + combined);
		}

		return new DefaultGatewayFilterChain(combined).filter(exchange);
	}

如何用它做的权限校验?

在实际业务中,可能用gateway来做登陆校验,以及访问权的判断,还可以配置白名单实现哪些路径可以忽略。

登陆校验:不同平台的登陆校验,校验token是否有效
访问权限校验:比如有没有功能权限码之类的,系统里面会为不同的人配置不同的功能权限
配置忽略路径:有些路径可能不需要上述2步校验,所以直接可以通过
可以写自定义一个GlobalFilter,重写invoke方法。

先处理ignore路径,用正则表达式匹配,如果符合匹配,则直接放行。
2. 如果不是白名单,则先进行登陆校验,再进行权限校验。
如何用gateway做限流(gateway自带一个令牌桶的限流器)

•  首先要配置route •  其次当请求命中了配置的uri路径后RequestRateLimiterGatewayFilterFactory的apply方法就会被执行 

spring:
  cloud:
    gateway:
      routes:
        - id: rate_limiter_route
          uri: http://backend-service:8080  # 目标服务地址
          predicates:
            - Path=/api/**                   # 匹配的请求路径
          filters:
            - name: RequestRateLimiter        # 使用限流过滤器
              args:
                # 1. 指定 Key 解析器(需注册为 Spring Bean)
                key-resolver: "#{@ipKeyResolver}"  
                # 2. Redis 限流器配置
                redis-rate-limiter: 
                  replenishRate: 10           # 每秒填充的令牌数
                  burstCapacity: 20           # 令牌桶容量
                  requestedTokens: 1          # 每次请求消耗的令牌数

 如何动态路由?

       自定义一个监听器,处理的event为refreshScopeEvent,nacos的配置发生改变后(会通知所有的监听器),拿到消息后,可以通过gateway提供的refresh方法去刷新r内存中的route。

自定义实现全局过滤器:

SignUtils:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;

/**
 * 签名的工具类
 */
public class SignUtils {

    public static List<String> splitString(String input, int chunkSize) {
        List<String> chunks = new ArrayList<>();
        for (int i = 0; i < input.length(); i += chunkSize) {
            int end = Math.min(input.length(), i + chunkSize);
            chunks.add(input.substring(i, end));
        }
        return chunks;
    }

    public static String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        }
    }

    public static String aesEncrypt(String input, String key) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(input.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
}
SignGlobalFilter:
import org.gateway.example.utils.SignUtils;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
public class SignGlobalFilter implements GlobalFilter, Ordered {

    private static final String AES_KEY = "mysecretkey12345!"; // 16字节 AES 密钥
    private static final List<String> WHITE_LIST = Arrays.asList(
            "/usp/out-api/auth/whiteApi",  // 示例白名单路径
            "/public"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();

        // 1. 白名单路径直接放行
        if (isWhiteListed(path)) {
            return chain.filter(exchange);
        }

        // 2. 装饰响应以处理 JSON 内容
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();

        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                // 3. 仅处理 JSON 响应
                if (isJsonResponse(this)) {
                    return Flux.from(body)
                            .collectList()
                            .flatMap(list -> {
                                // 4. 合并响应体内容
                                DataBuffer composite = bufferFactory.join(list);
                                byte[] content = new byte[composite.readableByteCount()];
                                composite.read(content);
                                DataBufferUtils.release(composite);
                                String responseBody = new String(content, StandardCharsets.UTF_8);

                                // 5. 生成签名逻辑
                                try {
                                    // 参数3:响应体 MD5
                                    String dataMd5 = SignUtils.md5(responseBody);
                                    List<String> param3 = SignUtils.splitString(dataMd5, 6);

                                    // 参数1:请求头中的签名
                                    String param1 = request.getHeaders().getFirst("X-Sign");
                                    if (param1 == null) {
                                        return Mono.error(new IllegalArgumentException("Missing X-Sign header"));
                                    }
                                    List<String> param1List = SignUtils.splitString(param1, 10);

                                    // 参数2:请求路径 MD5
                                    String urlMd5 = SignUtils.md5(path);
                                    List<String> param2 = SignUtils.splitString(urlMd5, 8);

                                    // 合并、排序、生成最终签名
                                    List<String> combined = new ArrayList<>();
                                    combined.addAll(param1List);
                                    combined.addAll(param2);
                                    combined.addAll(param3);
                                    combined.sort((a, b) -> b.compareTo(a)); // 降序排列

                                    String combinedMd5 = SignUtils.md5(String.join("", combined));
                                    String signature = SignUtils.aesEncrypt(combinedMd5, AES_KEY);

                                    // 6. 添加签名到响应头
                                    getHeaders().add("X-Signature", signature);
                                } catch (Exception e) {
                                    return Mono.error(e);
                                }

                                // 7. 重新写入响应体
                                return super.writeWith(Flux.just(bufferFactory.wrap(content)));
                            })
                            .onErrorResume(e -> {
                                originalResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
                                return originalResponse.writeWith(
                                        Flux.just(bufferFactory.wrap(e.getMessage().getBytes()))
                                );
                            });
                } else {
                    // 非 JSON 响应直接传递
                    return super.writeWith(body);
                }
            }
        };

        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    // 判断是否为白名单路径
    private boolean isWhiteListed(String path) {
        return WHITE_LIST.stream().anyMatch(path::startsWith);
    }

    // 判断是否为 JSON 响应
    private boolean isJsonResponse(ServerHttpResponse response) {
        MediaType contentType = response.getHeaders().getContentType();
        return contentType != null && contentType.includes(MediaType.APPLICATION_JSON);
    }

    @Override
    public int getOrder() {
        return -1; // 高优先级
    }
}
maven配置:
<?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 https://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>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springcloud</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.0</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--添加配置跳过测试-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

</project>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2373793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day22 Kaggle泰坦尼克号训练实战

​ 作业 自行学习参考如何使用kaggle平台&#xff0c;写下使用注意点&#xff0c;并对下述比赛提交代码 kaggle泰坦里克号人员生还预测 一、流程 思路概述 数据加载 &#xff1a;读取泰坦尼克号的训练集和测试集。数据预处理 &#xff1a;处理缺失值、对分类变量进行编码、…

深入浅出之STL源码分析7_模版实例化与全特化

1.引言 显示实例话和全特化的区别&#xff0c;之前我们在讨论类模版的时候&#xff0c;讨论过&#xff0c;他俩不是同一个概念&#xff0c;类模版中你如果全特化了&#xff0c;还是需要实例化才能生成代码&#xff0c;但是对于函数模版&#xff0c;这个是不同的&#xff0c;函…

2025 Mac常用软件安装配置

1、homebrew 2、jdk 1、使用brew安装jdk&#xff1a; brew install adoptopenjdk/openjdk/adoptopenjdk8 jdk默认安装位置在 /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home 目录。 2、配置环境变量&#xff1a; vim ~/.zshrc# Jdk export JAVA_HOM…

容器技术 20 年:颠覆、重构与重塑软件世界的力量

目录 容器技术发展史 虚拟化技术向容器技术转变 Docker的横空出世 容器编排技术与Kubernetes 微服务的出现与Istio 工业标准的容器运行时 容器技术与 DevOps 的深度融合​ 无服务架构推波助澜 展望未来发展方向 从 20 世纪硬件虚拟化的笨重&#xff0c;到操作系统虚拟…

cmake:test project

本文主要探讨cmake在测试和项目中的应用。 add_test add_test(NAME <name> COMMAND <command> [<arg>...] [CONFIGURATIONS <config>...] [WORKING_DIRECTORY <dir>] [COMMAND_EXPAND_LISTS])  add_test(NAME test_uni COMMAND $<TARGET_F…

C++开发过程中的注意事项详解

目录 C++开发过程中的注意事项详解 一、内存管理:避免泄漏与资源浪费 1.1 使用智能指针管理动态内存 1.2 避免手动内存管理的陷阱 1.3 利用RAII机制管理资源 1.4 容器与内存分配 二、安全性:防御攻击与未定义行为 2.1 输入验证与安全编码 2.2 使用安全的通信协议 2…

第六天:Java数组

数组 数组概述 数组是相同类型数据的有序集合。数组中的元素可以是任意数据类型&#xff0c;包括基本类型和引用类型数组描述是相同类型的若干个数据&#xff0c;按照一定的先后顺序排列组合而成。数组下标从0开始。 数组声明与创建 数组的声明 int[] nums;//声明一个数组…

李沐动手深度学习(pycharm中运行笔记)——09.softmax回归+图像分类数据集+从零实现+简洁实现

09.softmax回归图像分类数据集从零实现简洁实现&#xff08;与课程对应&#xff09; 目录 一、softmax回归 1、回归 vs 分类 2、经典分类数据集&#xff1a; 3、从回归到分类——均方损失 4、从回归到多类分类——无校验比例 5、从回归到多类分类——校验比例 6、softmax和…

Qt获取CPU使用率及内存占用大小

Qt 获取 CPU 使用率及内存占用大小 文章目录 Qt 获取 CPU 使用率及内存占用大小一、简介二、关键函数2.1 获取当前运行程序pid2.2 通过pid获取运行时间2.3 通过pid获取内存大小 三、具体实现五、写在最后 ​ 一、简介 近期在使用软件的过程中发现一个有意思的东西。如下所示&a…

嵌入式学习笔记 - 运算放大器的共模抑制比

一 定义 共模抑制比&#xff08;Common Mode Rejection Ratio, ‌CMRR‌&#xff09;是衡量差分放大器&#xff08;或差分电路&#xff09;抑制共模信号能力的关键指标。它在电子工程中尤为重要&#xff0c;特别是在需要处理微弱信号或对抗环境噪声的场景中。 核心概念 ‌共…

牛客周赛 Round 92-题解

牛客周赛 Round 92-题解 A-小红的签到题 code #include<iostream> #include<string> using namespace std; string s; int main() {int n;cin >> n;cout << "a_";for (int i 0; i < n - 2; i )cout << b;return 0; }B-小红的模…

【PVE】ProxmoxVE8虚拟机,存储管理(host磁盘扩容,qcow2/vmdk导入vm,vm磁盘导出与迁移等)

【PVE】ProxmoxVE8虚拟机&#xff0c;存储管理&#xff08;host磁盘扩容&#xff0c;qcow2/vmdk导入vm&#xff0c;vm磁盘导出与迁移等&#xff09; 文章目录 1、host 磁盘扩容2、qcow2/vmdk导入vm3、vm 磁盘导出与迁移 1、host 磁盘扩容 如何给host扩容磁盘&#xff0c;如增加…

Umi+React+Xrender+Hsf项目开发总结

一、菜单路由配置 1.umirc.ts 中的路由配置 .umirc.ts 文件是 UmiJS 框架中的一个配置文件&#xff0c;用于配置应用的全局设置&#xff0c;包括但不限于路由、插件、样式等。 import { defineConfig } from umi; import config from ./def/config;export default defineCon…

C# Newtonsoft.Json 使用指南

Newtonsoft.Json (也称为 Json.NET) 是一种适用于 .NET 的常用高性能 JSON 框架&#xff0c;用于处理 JSON 数据。它提供了高性能的 JSON 序列化和反序列化功能。 安装 通过 NuGet 安装 基本用法 1. 序列化对象为 JSON 字符串 using Newtonsoft.Json;var product new Prod…

数字电子技术基础(五十七)——边沿触发器

目录 1 边沿触发器 1.1 边沿触发器简介 1.1.1 边沿触发器的电路结构 1.3 边沿触发的D触发器和JK触发器 1.3.1 边沿触发的D型触发器 1.3.2 边沿触发的JK触发器 1 边沿触发器 1.1 边沿触发器简介 对于时钟触发的触发器来说&#xff0c;始终都存在空翻的现象&#xff0c;抗…

VC++ 获取CPU信息的两种方法

文章目录 方法一&#xff1a;使用 Windows API GetSystemInfo 和 GetNativeSystemInfo (基本信息)编译和运行代码解释 方法二&#xff1a;使用 __cpuid&#xff08;CPU序列号、特性等&#xff09;代码解释&#xff1a; 开发过程中需要使用 VC获取电脑CPU信息&#xff0c;先总结…

编程技能:字符串函数02,strcpy

专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏&#xff0c;故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 &#xff08;一&#xff09;WIn32 专栏导航 上一篇&#xff1a;编程技能&#xff1a;字符串函数01&#xff0c;引言 回到目录 …

特励达力科LeCroy推出Xena Freya Z800 800GE高性能的800G以太网测试平台

Xena Freya Z800 800GE 是由全球领先的测试与测量解决方案提供商特励达力科公司&#xff08;Teledyne LeCroy&#xff09;开发的高性能以太网测试平台&#xff0c;专为满足从10GE到800GE数据中心互连速度的需求而设计。特励达力科公司在网络测试领域拥有超过50年的技术积累&…

docker 日志暴露方案 (带权限 还 免费 版本)

接到了一个需求&#xff0c;需求的内容是需要将测试环境的容器暴露给我们的 外包同事&#xff0c;但是又不能将所有的容器都暴露给他们。 一开始&#xff0c;我分别找了 Portainer log-pilot dpanel 它们都拥有非常良好的界面和容器情况可视化。 但&#xff0c;缺点是&am…

【软考-高级】【信息系统项目管理师】【论文基础】风险管理过程输入输出及工具技术的使用方法

风险管理概念 项目风险是一种不确定的事件或条件&#xff0c;一旦发生&#xff0c;会对项目目标产生某种正面或负面的影响。项目风险既包括对项目目标的威胁&#xff0c;也包括促进项目目标的机会。 风险源于项目之中的不确定因素&#xff0c;项目在不同阶段会有不同的风险。…