spring gateway网关接口黑名单实现

news2024/10/22 17:03:52

1. 背景

灵活配置线上接口黑名单,可以临时调用

2. 网关路由配置application.yml

server:
  port: 8868
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域配置
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        # options请求 就是一种询问服务器是否浏览器可以跨域的请求
        # 如果每次跨域都有询问服务器是否浏览器可以跨域对性能也是损耗
        # 可以配置本次跨域检测的有效期maxAge
        # 在maxAge设置的时间范围内,不去询问,统统允许跨域
        corsConfigurations:
          '[/**]':
            allowedOriginPatterns: # 允许哪些网站的跨域请求
              - "*"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*"    # 允许在请求中携带的头信息
            allowCredentials: true    # 允许在请求中携带cookie
            maxAge: 360000            # 本次跨域检测的有效期(单位毫秒)
            # 有效期内,跨域请求不会一直发option请求去增大服务器压力
      routes: # 网关路由配置
        - id: route_to_provider #service服务,业务逻辑
          uri: lb://cloud-k8s-enterprise-service #通过服务发现找到 Service 服务
          predicates:
            - Path=/pay/**,/wx/callback/**
        - id: route_to_server #web服务 入口
          uri: lb://cloud-k8s-enterprise-server #通过服务发现找到 Service 服务
          predicates:
            - Path=/**
      #default-filters:
      # - name: BlacklistFilter
        
        #BlacklistFilter 在 default-filters 中使用时可能导致了错误。default-filters 是一个全局过滤器的配置,通常这些过滤器需要是 Spring Cloud Gateway 提供的预定义过滤器,而不是自定义的全局过滤器。你的 BlacklistFilter 是自定义的全局过滤器,不能直接放在 default-filters 中。
logging:
  level:
    org.springframework.cloud.gateway: DEBUG

2.1. 坑点:无法进行路由转发,配置格式;无法加载自定义全局过滤器

spring.cloud.gateway: 注意坑点!!!!!!!

这里如果单独把nacos配置拎出去,注释了,结果网关路由gateway缺少cloud这层配置,一直无法进行路由转发,网关配置未生效

server:
  port: 8868
spring:
#  application:
#    name: service-gateway
#  cloud:  注意坑点!!!!!!! 这里有人单独把nacos配置拎出去,注释了,结果网关路由gateway缺少cloud这层配置,一直无法进行路由转发,网关配置未生效
#    nacos:
#      discovery:
#        server-addr: 127.0.0.1:8848
    gateway:
      globalcors: # 全局的跨域配置

default-filters:

- name: BlacklistFilter

BlacklistFilter 在 default-filters 中使用时可能导致了错误。default-filters 是一个全局过滤器的配置,通常这些过滤器需要是 Spring Cloud Gateway 提供的预定义过滤器,而不是自定义的全局过滤器。你的 BlacklistFilter 是自定义的全局过滤器,不能直接放在 default-filters 中。

可以配置在某个单独的服务下

#单服务黑名单网关配置demo
spring:
  cloud:
    gateway:
      routes:
        - id: dynamic_blacklist_route
          uri: http://localhost:8080
          predicates:
            - Path=/api/v1/**
          filters:
            - name: BlacklistFilter
server:
  port: 8868
spring:
#  application:
#    name: service-gateway
#  cloud:  注意坑点!!!!!!! 这里有人单独把nacos配置拎出去,注释了,结果网关路由gateway缺少cloud这层配置,一直无法进行路由转发,网关配置未生效
#    nacos:
#      discovery:
#        server-addr: 127.0.0.1:8848
    gateway:
      globalcors: # 全局的跨域配置
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        # options请求 就是一种询问服务器是否浏览器可以跨域的请求
        # 如果每次跨域都有询问服务器是否浏览器可以跨域对性能也是损耗
        # 可以配置本次跨域检测的有效期maxAge
        # 在maxAge设置的时间范围内,不去询问,统统允许跨域
        corsConfigurations:
          '[/**]':
            allowedOriginPatterns: # 允许哪些网站的跨域请求
              - "*"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*"    # 允许在请求中携带的头信息
            allowCredentials: true    # 允许在请求中携带cookie
            maxAge: 360000            # 本次跨域检测的有效期(单位毫秒)
            # 有效期内,跨域请求不会一直发option请求去增大服务器压力
      routes: # 网关路由配置
        - id: route_to_provider #service服务,业务逻辑
          uri: lb://cloud-service #通过服务发现找到 Service 服务
          predicates:
            - Path=/apiv1/**,/apiv2/**
        - id: route_to_server #web服务 入口
          uri: lb://cloud-server #通过服务发现找到 Service 服务
          predicates:
            - Path=/**
logging:
  level:
    org.springframework.cloud.gateway: DEBUG

2.2. 帮助文档

Spring Colud gateway 网关引入转发无效 (404)_gateway_虔浅-云原生

跟着大佬们的文章,想玩一下gateway api网关。经过一系列ctrl+c和ctrl+v的操作,项目的基本就搭建好了;

1.引入依赖 pom.xml

<!--网关依赖-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--不要引入web!不要引入web!不要引入web!gateway中已经包含-->

2.创建启动类

//如果有注册机(nacos,eureka)什么的可以不用引入
//@EnableDiscoveryClient,只需在配置文件里面配置好就行
@SpringBootApplication
public class GatewayServer {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServer.class,args);
    }
}

3.配置文件

server:
    port: 9999                    #服务端口

spring:
    application:
        name: gateway      #服务名称
    cloud:
        ############# nacos配置中心 start (没有注册机可以不用配置这一块) #############
        nacos:
            # nacos配置中心    #nacos的配置文件名称(Data Id)叫 服务名称.yml ,
            # 组名(Group)
            config:
                server-addr: http://nacos服务器
                file-extension: yml
                namespace: xxx #命名空间(md5)
                group: 分组名    #分组
            #发现配置
            discovery:
                server-addr: http://www.lang9725.fun/find/
                namespace:  xxx #命名空间(md5)
                group: batw
        ############# nacos配置中心 end #############

        ############## 网关配置 start ##############
        gateway:
            #开启网关,和很多地方说不一样,很多地方都是这个是默认开启的,
            #但设置的话网关功能将无效
            enabled: true
            routes:
                - id: server_finance        #id 唯一即可
                  uri: http://localhost:44444  #用转发路径
                  predicates:
                      - Path=/test/test/**     # **表示转发地址下的全部都可以通过 
        ############## 网关配置 end ##############

这里要一个被转发服务器地址: http://localhost:44444/test/test/任意地址 并保证这个地址没问题,我们的测试网关地址:http://localhost:9999/test/test/任意地址,保证两个地址的返回效果一致(负载均衡效果到达预期)

前期没有配置spring.cloud.gateway.enabled=true,测试网关一直到报404,看了很多大佬debug,不明所以,最后发现是这边配置没有加,而是配置一个spring.cloud.gateway.discovery.locator.enabled=true,后面加了spring.cloud.gateway.enabled=true就可以了,这个东西应该是和版本,和依赖包一定联系吧。

3. nacos配置bootstrap.yml

spring: #springboot的配置
  application:
    name: cloud-gateway
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: cloud-local
        file-extension: yml
        shared-configs:
          - data-id: blacklist.yml # 在 Nacos 中的配置 Data ID
            refresh: true           # 启用动态刷新
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: cloud-local 

4. 黑名单配置blacklist.yml

blacklist:
  paths:
    - /coupon/manualGrantPowerCard
    - /coupon/manualGrantVoucher

4.1. 加载配置类BlacklistConfig.java

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

import java.util.List;

/**
 * @author shenlifang
 * @since 黑名单配置类
 * @date 2024/10/21 11:16
 */
@Component
@RefreshScope
@ConfigurationProperties(prefix = "blacklist")
public class BlacklistConfig {
    private List<String> paths;

    // Getter 和 Setter
    public List<String> getPaths() {
        return paths;
    }

    public void setPaths(List<String> paths) {
        this.paths = paths;
    }
}

5. 启动类,扫描包GatewayApplication.java


import com.ly.cloud.gateway.config.BlacklistConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigurationProperties(BlacklistConfig.class)
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

6. 自定义全局过滤器BlacklistFilter.java

package com.cloud.gateway.config;

import com.fasterxml.jackson.core.JsonProcessingException;
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.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author shenlifang
 * @date 2024/10/21 11:12
 * @since 接口黑名单过滤器
 */
@Component
public class BlacklistFilter implements GlobalFilter, Ordered {

    // 黑名单接口路径列表,可以通过配置文件或者数据库来动态管理
    private final BlacklistConfig blacklistConfig;

    // 通过构造器注入 BlacklistConfig
    public BlacklistFilter(BlacklistConfig blacklistConfig) {
        this.blacklistConfig = blacklistConfig;
    }

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

        // 获取配置中的黑名单路径
        List<String> blacklistedPaths = blacklistConfig.getPaths();

        // 检查请求路径是否在黑名单中
        if (isBlacklistedPath(path, blacklistedPaths)) {
            // 如果匹配黑名单,返回 403 Forbidden
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);

            // return exchange.getResponse().setComplete();

            // 设置响应头,表示内容类型为 JSON
            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);

            // 返回描述信息
            // String responseBody = "{\"successful\":false,\"code\":403,\"message\":\"接口服务禁用: 该接口服务已加入黑名单,禁止访问!\"}";
            String responseBody = "{\"error\": \"Access denied: The requested path is blacklisted.\"}";


            // 写入响应体
            DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
            DataBuffer buffer = bufferFactory.wrap(responseBody.getBytes(StandardCharsets.UTF_8));

            // 完成响应
            return exchange.getResponse().writeWith(Mono.just(buffer));

        }
        // 如果没有匹配黑名单,继续执行后续过滤器链
        return chain.filter(exchange);
    }

    // 判断路径是否在黑名单中
    private boolean isBlacklistedPath(String path, List<String> blacklistedPaths) {
        return blacklistedPaths.stream().anyMatch(path::startsWith);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

7. 优化改造:优先从redis中获取

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class BlacklistFilter implements GlobalFilter, Ordered {

    private final BlacklistConfig blacklistConfig;
    private final RedisTemplate<String, List<String>> redisTemplate;

    // 通过构造器注入 BlacklistConfig 和 RedisTemplate
    public BlacklistFilter(BlacklistConfig blacklistConfig, RedisTemplate<String, List<String>> redisTemplate) {
        this.blacklistConfig = blacklistConfig;
        this.redisTemplate = redisTemplate;
    }

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

        // 优先从 Redis 中获取黑名单路径
        List<String> blacklistedPaths = redisTemplate.opsForValue().get("blacklist_paths");

        // 如果 Redis 中没有黑名单路径,则从配置文件中获取并缓存到 Redis
        if (blacklistedPaths == null || blacklistedPaths.isEmpty()) {
            blacklistedPaths = blacklistConfig.getPaths();

            // 将配置文件中的黑名单缓存到 Redis,有效期设置为 10 分钟
            redisTemplate.opsForValue().set("blacklist_paths", blacklistedPaths, 10, TimeUnit.MINUTES);
        }

        // 检查请求路径是否在黑名单中
        if (isBlacklistedPath(path, blacklistedPaths)) {
            // 如果匹配黑名单,返回 403 Forbidden
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }

        // 如果没有匹配黑名单,继续执行后续过滤器链
        return chain.filter(exchange);
    }

    // 判断路径是否在黑名单中
    private boolean isBlacklistedPath(String path, List<String> blacklistedPaths) {
        return blacklistedPaths.stream().anyMatch(path::startsWith);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

8. 网关启动过程中日志打印网关配置属性

如果没打印,查看yaml文件配置是否有问题

2024-10-21 17:14:02.677 DEBUG 8754 --- [ main] o.s.c.gateway.config.GatewayProperties : Routes supplied from Gateway Properties: [RouteDefinition{id='route_to_provider', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/apiv1/**, _genkey_1=/apiv2/**}}], filters=[], uri=lb://cloud-service, order=0, metadata={}}, RouteDefinition{id='route_to_server', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/**}}], filters=[], uri=lb://cloud-server, order=0, metadata={}}]

日志中的 o.s.c.gateway.config.GatewayProperties 是 Spring Cloud Gateway 在启动过程中输出的日志。具体解释如下:

  • o.s.c.gateway.config.GatewayProperties:这是类的名称,表示 org.springframework.cloud.gateway.config.GatewayProperties。这个类负责管理 Spring Cloud Gateway 的配置属性,包括网关的路由、过滤器等信息。
  • Routes supplied from Gateway Properties::这条日志表明,Spring Cloud Gateway 已经从应用的配置文件(例如 application.yml 或者 bootstrap.yml)中读取并加载了路由配置。

  • RouteDefinition:表示每一条路由定义。
    • id:路由的唯一标识符,例如 route_to_providerroute_to_server
    • predicates:路由谓词,用来匹配请求路径。例如,Path 谓词匹配 /apiv1/**/apiv2/** 等路径。
    • filters:表示应用在路由上的过滤器,在当前配置中为空(没有应用额外的过滤器)。
    • uri:目标 URI,lb://cloud-service 表示通过负载均衡(LoadBalancer)转发请求到服务 cloud-service
    • order:路由的优先级,值越小优先级越高。
    • metadata:存储路由的元数据,当前为空。

Spring Cloud Gateway 从配置文件中正确读取并加载了两个路由定义,分别是 route_to_providerroute_to_server,并根据配置的路径规则(Path)匹配请求并进行转发。

如果路由匹配不正确,可以通过查看这些日志确认路由是否如预期加载。

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

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

相关文章

uni-app中添加自定义相机(微信小程序+app)

一、微信小程序中 微信小程序中可以直接使用camera标签&#xff0c;这个标签不兼容app&#xff0c;官方文档 <cameradevice-position"back"flash"off":style"{ height: lheight px, width: lwidth px }"class"w-full"></c…

vue3【实战】 渲染 md 文件(markdown语法 .md后缀的文件)

1. 安装相关插件 npm i unplugin-vue-markdown markdown-it-prism prism unhead/vue2. 添加配置 src/main.ts // 给 md 文件创建头部 import { createHead } from unhead/vue // md 文件中代码高亮的样式 import prismjs/themes/prism.css // 自定义 md 文件的样式 import /as…

【JAVA面试题】什么是Springboot的自动配置以及注意事项

文章目录 强烈推荐核心概念&#xff1a;自动配置的关键特点&#xff1a;示例&#xff1a; 需要注意的点1.默认配置可能不适合所有场景2.Bean 冲突与覆盖3.应用启动慢的问题4.过度依赖自动配置5.安全性问题6.依赖冲突与版本兼容7.过多不必要的自动配置8.调试困难 专栏集锦 强烈推…

.net framework 3.5sp1安装错误卡住不动怎么解决

解决 .NET Framework 3.5 SP1 安装错误卡住的问题&#xff0c;可以尝试以下几种方法&#xff1a; 1.使用 DISM 工具&#xff1a; 将下载的 NetFx3.cab 文件放置在 C:\Windows 文件夹下。 以管理员身份打开命令提示符&#xff0c;输入以下命令&#xff1a; dism /online /En…

【web前端设计】jquery图标动画特效

学习目标 学习web前端设计技术&#xff08;HTML、css、JavaScript、jQuery等&#xff09;&#xff0c;综合运用技术&#xff0c;将其与HTML元素结合&#xff0c;设计样式、监听事件、添加动画等&#xff0c;给用户呈现出更好的视觉交互效果。本文主要学习分页按钮自动放大、元…

HCIP-HarmonyOS Application Developer 习题(十二)

&#xff08;多选&#xff09;1、声明式开发范式的转场动画包含以下哪几种类型? A、页面间转场 B、应用间转场 C、共享元素转场 D、组件内转场 答案&#xff1a;ACD 分析&#xff1a; &#xff08;多选&#xff09;2、公共事件服务为应用程序提供哪些能力。 A、取消发布公共…

IPMA能力基础线是什么?项目管理工具有哪些应用场景?

作为资深团队管理者&#xff0c;常常面临如何提升团队项目管理能力的挑战。其实在现代的项目管理世界中&#xff0c;有许多标准和模型帮助我们更好地理解项目的复杂性&#xff0c;IPMA&#xff08;International Project Management Association&#xff09;能力基础线就是其中…

iOS静态库(.a)及资源文件的生成与使用详解(Swift版本)

引言 在 iOS 开发中&#xff0c;开发者常常需要将一些功能模块封装成可重用的库&#xff0c;以便在多个项目中共享使用。除了常见的Framework&#xff08;动态库/静态库&#xff09;&#xff0c;静态库&#xff08;.a文件&#xff09;也是一种非常实用的封装方式。静态库在编译…

Java全栈经典面试题剖析4】JavaSE高级 -- 包装类,String, 类方法

目录 面试题3.1 什么是自动装箱与拆箱&#xff1f;用什么方式来装箱与拆箱&#xff1f; 面试题3.2 int和Integer有什么区别&#xff1f; 面试题3.3 Integer常量池 面试题3.4 字符串常量池 面试题3.5 这句代码创建了几个对象? String str1 new String("xyz");…

前端拦截302重定向

背景: 根据业务场景需要拦截302做后续的逻辑处理 尝试一: : axios拦截 、、、、、async created() {// 获取302请求返回的location后手动修改video的src路径let targetSrc;try {await axios.get(this.video).then((res) > {const { headers, status } res;const { locat…

Android 图片相识度比较(pHash)

概述 在 Android 中&#xff0c;要比对两张 Bitmap 图片的相似度&#xff0c;常见的方法有基于像素差异、直方图比较、或者使用一些更高级的算法如 SSIM&#xff08;结构相似性&#xff09;和感知哈希&#xff08;pHash&#xff09;。 1. 基于像素的差异比较 可以逐像素比较…

学习笔记——Test.pwn

前言&#xff1a;笔者也才接触Pwn&#xff0c;写这篇wp&#xff0c;记录目前所得感悟、思考、理解等。 存在错误&#xff0c;或者解释不通的地方&#xff0c;还请提出&#xff0c;已补足笔记的缺陷。 Pwn是什么&#xff1f; 我Pwn掉了你的电脑、我Pwn掉了你的设备…… 通俗的…

重庆大学软件工程考研,难度如何?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 重大软件专业可谓是最好上岸的985院校&#xff01;重庆大学24考研各大学院复试录取情况已出&#xff0c; 我们先说学硕部分&#xff1a; 招生人数&#xff1a; 重庆大学软件工程学硕近几年计划统招人数都不多&#xf…

入选ECCV 2024!浙江大学联合微软亚洲研究院提出统一医学图像预训练框架UniMedI,打破医学数据异构化藩篱

让 AI 在某些条件下具备类似人类的反应能力&#xff0c;从而代替人类高效地从事特定工作&#xff0c;是 AI 领域研究人员孜孜不倦的追求。正如在医学图像和人工智能的交叉领域&#xff0c;基于视觉语言预训练的深度模型 (Visual-Language Pre-training, VLP) 凭借其自动化的特点…

Docker本地镜像发布到阿里云镜像服务的简易指南

1 阿里云容器镜像服务 阿里云容器镜像服务&#xff08;Alibaba Cloud Container Registry&#xff0c;简称ACR&#xff09;是一个为容器镜像、Helm Chart等云原生资产提供安全托管及高效分发的平台。它支持多架构容器镜像&#xff0c;包括Linux、Windows、ARM等&#xff0c;以…

心觉:感恩日记:每天5分钟,重新定义你的人生

​Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作207/1000天 你是否觉得生活节奏太快&#xff0c;总是有做不完的事、解决不完的问题&#xff1f; 有一个简单的方法&#xff0c;…

DEPT_ DECOMPOSED PROMPT TUNING FOR PARAMETER-EFFICIENT FINE-TUNING

论文汇总 当前的问题 (1)Prompt Tuning通常收敛缓慢&#xff0c;并且对初始化敏感&#xff1b; (2)Prompt Tuning延长了输入序列的总长度&#xff0c;从而加剧了计算需求(即训练/推理时间和内存成本)&#xff0c;这是由于Transformer的二次复杂度(Vaswani et al, 2017)。 解…

机器视觉系统硬件组成之工业相机篇

工业相机是一种非常重要的机器视觉器件&#xff0c;它能够将被采集的图像信息通过电路转换成电信号&#xff0c;再通过模数转换器&#xff08;ADC&#xff09;将其转化为数字信号&#xff0c;最后以标准的视频信号输出。工业相机在机器视觉领域得到了广泛应用&#xff0c;包括质…

springboot055服装生产管理的设计与实现(论文+源码)_kaic

毕业设计(论文) 协力服装厂服装生产管理系统 的设计与实现 学生姓名 XXX 学 号 XXXXXXXX 分院名称 XXXXXXXX 专业班级 XXXXX 指导教师 XXXX 填写…

CROss PlatformS (CROPS) 与 Docker

CROPS 是一个开源的、跨平台的开发框架&#xff0c;专为利用 Docker 容器在 Windows、macOS 和 Linux 系统上创建和管理构建主机而设计。它简化了在非 Linux 系统上运行 Yocto 项目及其他基于 Linux 的工具的过程&#xff0c;同时提供了一个可扩展的开发环境&#xff0c;支持多…