云原生 envoy xDS 动态配置 java控制平面开发 支持restful grpc实现 EDS 动态endpoint配置

envoy xDS 动态配置 java控制平面开发 支持restful grpc 动态endpoint配置

大纲

  • 基础概念
  • Envoy 动态配置API
  • 配置方式
  • 动静结合的配置方式
  • 纯动态配置方式
  • 实战

基础概念

Envoy 的强大功能之一是支持动态配置,当使用动态配置时,我们不需要重新启动 Envoy 进程就可以生效。Envoy 通过从磁盘文件或网络接口读取配置,动态地重新加载配置。动态配置使用所谓的发现服务 API,指向配置的特定部分。这些 API 也被统称为xDS 即 (xxx discovery service)

注意:
Envoy的发现API开发模式是,按照Envoy指定的接口名称,请求参数,响应值,自己开发,即需要满足Envoy的规范

Envoy动态配置支持文件方式,grpc接口和, restful接口,其中 grpc接口/REST接口 的配置提供者(自己开发的项目)也被称为控制平面

实现方式:

  • 文件方式: 监听文件的变化动态修改
  • grpc接口: 使用的tcp长连接
  • REST接口: 使用的http轮询的方式实现

Envoy 动态配置API

API类型

Envoy 内部有多个发现服务 API (xDS):

  • 监听器发现服务(LDS listener discovery service) 使用 LDS,Envoy 可以在运行时发现监听器,包括所有的过滤器栈、HTTP 过滤器和对 RDS 的引用。(即动态配置 listener 类似nginx配置虚拟主机)
  • 扩展配置发现服务(ECDS) 使用 ECDS,Envoy 可以独立于监听器获取扩展配置(例如,HTTP 过滤器配置)。
  • 路由发现服务(RDS route discovery service) 使用 RDS,Envoy 可以在运行时发现 HTTP 连接管理器过滤器的整个路由配置。与 EDS 和 CDS 相结合,我们可以实现复杂的路由拓扑结构。(即动态配置路由)
  • 虚拟主机发现服务(VHDS) 使用 VHDS 允许 Envoy 从路由配置中单独请求虚拟主机。当路由配置中有大量的虚拟主机时,就可以使用这个功能。
  • 宽泛路由发现服务(SRDS) 使用 SRDS,可以把路由表分解成多个部分。当有大的路由表时,就可以使用这个 API。
  • 集群发现服务(CDS cluster discovery service ) 使用 CDS,Envoy 可以发现上游集群。Envoy 将通过排空和重新连接所有现有的连接池来优雅地添加、更新或删除集群。Envoy 在初始化时不必知道所有的集群,因为我们可以在以后使用 CDS 配置它们。(即动态配置集群)
  • 端点发现服务(EDS endpoint discovery service) 使用 EDS,Envoy 可以发现上游集群的成员。 (即动态配置后端服务类似nginx upstream)
  • 秘密发现服务(SDS) 使用 SDS,Envoy 可以为其监听器发现秘密(证书和私钥,TLS 会话密钥),并为对等的证书验证逻辑进行配置。
  • 运行时发现服务(RTDS) 使用 RTDS,Envoy 可以动态地发现运行时层。

API 版本

Envoy 的 API 有 v2 v3 目前主流版本是 v3

官方文档 https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/xds_api

xDS API 可以使用restful接口和grpc接口开发,只要满足指定的接口名称和DiscoveryRequest,DiscoveryResponse参数和响应对象即可

例如以下就是一个EDS的接口
/v3/discovery:endpoints (即自己写的controller的mapping是/v3/discovery:endpoints)
@RequestMapping("/v3/discovery:endpoints")

配置方式

动静结合的配置方式

静态配置与动态配置结合

例如

static_resources:
  listeners:
  - name: my_listener
    address:
      socket_address: 
        protocol: TCP
        address: 0.0.0.0
        port_value: 15200
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: my-http-filter
          http_filters:
                - name: envoy.filters.http.router
          stat_prefix: my_listener_http
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:  
                  prefix: "/" 
                route:  
                  cluster: user-service 
  clusters:
    - name: user-service
      type: EDS #这里就是使用动态配置的方式实现endpoint的动态发现
      connect_timeout: 0.5s
      eds_cluster_config: 
        eds_config:
          resource_api_version: V3
          api_config_source:
            api_type: REST
            transport_api_version: V3
            cluster_names: [edscluster]
            refresh_delay: 10s 
    - name: edscluster
      type: STATIC
      connect_timeout: 0.5s
      hosts:  
        - socket_address: 
             address: 192.168.0.218
             port_value: 7590 

纯动态配置方式

使用dynamic_resources 配置动态内容

例如

dynamic_resources:
  ads_config:
    api_type: GRPC
    transport_api_version: V3
    grpc_services:
    - envoy_grpc:
        cluster_name: xds_cluster
  cds_config:
    resource_api_version: V3
    api_config_source:
      api_type: GRPC
      transport_api_version: V3
      grpc_services:
      - envoy_grpc:
          cluster_name: xds_cluster
  lds_config:
    resource_api_version: V3
    api_config_source:
      api_type: GRPC
      transport_api_version: V3
      grpc_services:
      - envoy_grpc:
          cluster_name: xds_cluster

当envoy没有读取到配置时会一直使用默认的配置,所以如果控制平面宕机后还是会保持配置

每个 xDS API 都有给定的资源类型:

v2版本

LDS :  envoy.api.v2.Listener
RDS : envoy.api.v2.RouteConfiguration
CDS : envoy.api.v2.Cluster
EDS :envoy.api.v2.ClusterLoadAssignment (EDS就是配置 endpoint)

v3版本

envoy.config.listener.v3.Listener
envoy.config.route.v3.RouteConfiguration,
envoy.config.route.v3.ScopedRouteConfiguration,
envoy.config.route.v3.VirtualHost
envoy.config.cluster.v3.Cluster
envoy.config.endpoint.v3.ClusterLoadAssignment  (EDS endpoint 返回的resources 对象类型)
envoy.extensions.transport_sockets.tls.v3.Secret
envoy.service.runtime.v3.Runtime

即接口返回DiscoveryResponse 内部的resources 是以上类型

实战

本次测试 envoy的版本为v1.16.0 使用docker镜像部署

在这里插入图片描述

基于envoy xDS api v3版本 java restful实现

step1 配置 envoy.yaml

配置文件如下

node:
  cluster: mycluster
  id: test-id


# 这是一段静态配置
static_resources:
  listeners:
  - name: my_listener
    address:
      socket_address: 
        protocol: TCP
        address: 0.0.0.0
        port_value: 15200 #配置一个静态的listener 监听来自任意IP的请求15200端口的http请求
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager #注意指定filters
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: my-http-filter
          http_filters:
                - name: envoy.filters.http.router
          stat_prefix: my_listener_http
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]  #任意域名的请求
              routes:
              - match:  
                  prefix: "/"   # 任意url的请求
                route:  
                  cluster: user-service  # 路由到user-service 集群
  
  # 配置集群    
  clusters:
    - name: user-service
      type: EDS  #模式指定为EDS  
      connect_timeout: 0.5s # 配置连接超时时间
      eds_cluster_config: 
        eds_config:
          resource_api_version: V3 #指定使用V3版本接口
          api_config_source:
            api_type: REST  #使用restful的方式
            transport_api_version: V3   #指定使用V3版本接口
            cluster_names: [edscluster]
            refresh_delay: 10s  # 配置刷新频率
      # 这里配置的是envoy EDS接口的提供服务即控制平面       
    - name: edscluster
      type: STATIC
      connect_timeout: 0.5s # 配置连接超时时间
      # envoy会去请求 192.168.0.218:7590/v3/discovery:endpoints 这个接口 获取endpoint配置信息
      # 代码见 my-docker-demo-envoy-plane/DataPlaneEndpointControllerV3.java
      hosts:  
        - socket_address: 
             address: 192.168.0.218
             port_value: 7590  

启动 envoy 镜像

docker run  -p 5201:5201 -p 15200:15200  -v /ops/envoy:/etc/envoy  envoyproxy/envoy:v1.16.0 

envoy 启动后可以看到开始调用 EDS接口,由于还没启动服务此时会报错

在这里插入图片描述

step2 java 程序开发

EDS接口使用java springboot 开发

注意点如下:

  • 1 接口必须是 /v3/discovery:endpoints
  • 2 动态配置需要是一个json 字符串 并且满足endpoint需要的格式
  • 3 返回值必须是一个DiscoveryResponse service.discovery.v3.DiscoveryResponse

DiscoveryResponse 格式如下

{
  "version_info": ...,
  "resources": [],
  "type_url": ...,
  "nonce": ...,
  "control_plane": {...}
}

整体的返回值json字符串如下

{
  "versionInfo": "1.0.0",
  "resources": [{
    "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
    "clusterName": "user-service",
    "endpoints": [{
      "lbEndpoints": [ {
        "endpoint": {
          "address": {
            "socketAddress": {
              "address": "10.244.1.203",
              "portValue": 5588
            }
          }
        }
      }]
    }]
  }]
}

如果自己拼接json字符串感觉比较麻烦,可以使用envoy-api包

<dependency>
		    <groupId>io.envoyproxy.controlplane</groupId>
		    <artifactId>api</artifactId>
		    <version>1.0.39</version>
		</dependency>

这个包,里面有xDS中的各种资源对象 以及grpc接口

也可以使用官方提供的 java控制面板项目 打包编译后得到api包,里面也有xDS中的各种资源对象

在这里插入图片描述

java 代码如下

  @RequestMapping(value="/v3/discovery:endpoints" , produces = {"application/json;charset=UTF-8"})
	public String discovery(HttpServletRequest req) throws Exception {			
		//json 字符串拼接
		//String json = staticJson();
		/**
		 * 构建返回EDS 配置json 字符串
		 */
		String json = useBean();
		
		return json;
	}
	
	/**
	 * @return
	 */
	private String useBean() throws Exception {
		/**
		 * 以下资源类出自
		 * 
		 * <dependency>
		    <groupId>io.envoyproxy.controlplane</groupId>
		    <artifactId>api</artifactId>
		    <version>1.0.39</version>
		</dependency>
		 * 
		 */
		//配置上游服务(类似nginx upstream)
		SocketAddress sa1 = SocketAddress.newBuilder().setAddress("10.244.0.190").setPortValue(5588).build();
		SocketAddress sa2 = SocketAddress.newBuilder().setAddress("10.244.1.203").setPortValue(5588).build();
		Address address1 = Address.newBuilder().setSocketAddress(sa1).build();
		Address address2 = Address.newBuilder().setSocketAddress(sa2).build();
		Endpoint end1 = Endpoint.newBuilder().setAddress(address1).build();
		Endpoint end2 = Endpoint.newBuilder().setAddress(address2).build();
	    LbEndpoint lb1 = LbEndpoint.newBuilder().setEndpoint(end1).build();
	    LbEndpoint lb2 = LbEndpoint.newBuilder().setEndpoint(end2).build();
		LocalityLbEndpoints llb = LocalityLbEndpoints.newBuilder().addLbEndpoints(lb1).addLbEndpoints(lb2).build();
		ClusterLoadAssignment cla = ClusterLoadAssignment.newBuilder()
				.setClusterName("user-service")
				.addEndpoints(llb).build();
		DiscoveryResponse dr = DiscoveryResponse.newBuilder().setVersionInfo("1.0.0")
				.addResources(Any.pack(cla))
				.build();
		
		JsonFormat.TypeRegistry typeRegistry = JsonFormat.TypeRegistry.newBuilder()
		        .add(ClusterLoadAssignment.getDescriptor())
		        .build();
		
		JsonFormat.Printer printer = JsonFormat.printer()
		        .usingTypeRegistry(typeRegistry);
		
		return printer.print(dr);
}

基于 envoy xDS api v3版本 java grpc实现

grpc的关键

  • 1 使用envoy api包 实现对应的grpc 服务
  • 2 返回值需要指定typeUrl
  • 3 配置文件需要加入 http2_protocol_options 指定使用http2

没使用http2_protocol_options 配置会出现如下异常

io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Unexpected HTTP/1.x request: POST /envoy.service.endpoint.v3.EndpointDiscoveryService/StreamEndpoints 
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:109) ~[grpc-netty-shaded-1.48.1.jar:1.48.1]
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:302) ~[grpc-netty-shaded-1.48.1.jar:1.48.1]
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239) ~[grpc-netty-shaded-1.48.1.jar:1.48.1]
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438) [grpc-netty-shaded-1.48.1.jar:1.48.1]

在这里插入图片描述

返回值未指定typeUrl

023-08-16 06:14:49.608][8][warning][config] [source/common/config/grpc_mux_impl.cc:155] Ignoring the message for type URL  as it has no current subscribers.

在这里插入图片描述

关键配置 envoy.yaml 如下

# 指定集群名称
# 动态配置需要指定节点集群名称        
node:
  cluster: mycluster
  id: test-id


# 这是一段静态配置
static_resources:
  listeners:
  - name: my_listener
    address:
      socket_address: 
        protocol: TCP
        address: 0.0.0.0
        port_value: 15200 #配置一个静态的listener 监听来自任意IP的请求15200端口的http请求
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager #注意指定filters
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: my-http-filter
          http_filters:
            - name: envoy.filters.http.router
          stat_prefix: my_listener_http
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]  #任意域名的请求
              routes:
              - match:  
                  prefix: "/"   # 任意url的请求
                route:  
                  cluster: user-service  # 路由到user-service 集群
  
  # 配置集群    
  clusters:
    - name: user-service
      type: EDS  #模式指定为EDS  
      connect_timeout: 0.5s # 配置连接超时时间
      eds_cluster_config: 
        eds_config:
          resource_api_version: V3 #指定使用V3版本接口
          api_config_source:
            api_type: GRPC  #使用grpc的方式
            transport_api_version: V3   #指定使用V3版本接口
            # 指定grpc_services 对应的集群
            # 这里将使用下面定义的集群
            grpc_services: 
              - envoy_grpc: 
                  cluster_name: edscluster
           
    # 这里配置的是envoy EDS接口的提供服务即控制平面       
    - name: edscluster
      type: STATIC
      connect_timeout: 0.5s # 配置连接超时时间
      # 这里是一个关键,必须指定http2_protocol_options 即使用http2
      http2_protocol_options: {}
      hosts:  
        - socket_address: 
             address: 192.168.0.218
             port_value: 7899  

关键java代码

public class EndpointDiscoveryServiceGrpcImpl extends EndpointDiscoveryServiceGrpc.EndpointDiscoveryServiceImplBase {

	
	/**
	 * 这个接口是客户端模式
	 * 
	 */
	@Override
	public io.grpc.stub.StreamObserver<io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest> streamEndpoints(
	        io.grpc.stub.StreamObserver<io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse> responseObserver) {
	     System.out.println("run grpc ...");
	     /**
	      * 创建StreamObserver<DiscoveryRequest>对象
	      */
		StreamObserver<DiscoveryRequest> so = new StreamObserver<DiscoveryRequest>() {

			@Override
			public void onNext(DiscoveryRequest request) {
				 //接收客户端每一次发送的数据,返回给客户端
				//showRequest(request);
				
			    SocketAddress sa1 = SocketAddress.newBuilder().setAddress("10.244.0.214").setPortValue(5588).build();
				SocketAddress sa2 = SocketAddress.newBuilder().setAddress("10.244.0.201").setPortValue(5588).build();
				Address address1 = Address.newBuilder().setSocketAddress(sa1).build();
				Address address2 = Address.newBuilder().setSocketAddress(sa2).build();
				Endpoint end1 = Endpoint.newBuilder().setAddress(address1).build();
				Endpoint end2 = Endpoint.newBuilder().setAddress(address2).build();
			    LbEndpoint lb1 = LbEndpoint.newBuilder().setEndpoint(end1).build();
			    LbEndpoint lb2 = LbEndpoint.newBuilder().setEndpoint(end2).build();
				LocalityLbEndpoints llb = LocalityLbEndpoints.newBuilder().addLbEndpoints(lb1).addLbEndpoints(lb2).build();
				ClusterLoadAssignment cla = ClusterLoadAssignment.newBuilder()
						 /**
				         * 这里配置的ClusterName 应该是路由对应的cluster name 而不是 node中的cluster
				         * route: { cluster: user-service }
				         */
						.setClusterName("user-service") 
						.addEndpoints(llb).build();
				final DiscoveryResponse dr = DiscoveryResponse.newBuilder().setVersionInfo("1.0.0")							
						.setTypeUrl("type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment")
						.addResources(Any.pack(cla))
						.build();
				
				/**
				 * 客户端模式这里不会去关闭StreamObserver
				 * 即不会调用 responseObserver.onCompleted();方法
				 */
				responseObserver.onNext(dr);
				
				System.out.println("send DiscoveryResponse ");
			}

			@Override
			public void onError(Throwable t) {
				System.out.println("onError");
				 t.printStackTrace();
			}

			@Override
			public void onCompleted() {
				 //当客户端数据发送完毕后调用此方法,返回客户端
				
				 SocketAddress sa1 = SocketAddress.newBuilder().setAddress("10.244.0.214").setPortValue(5588).build();
				SocketAddress sa2 = SocketAddress.newBuilder().setAddress("10.244.0.201").setPortValue(5588).build();
				Address address1 = Address.newBuilder().setSocketAddress(sa1).build();
				Address address2 = Address.newBuilder().setSocketAddress(sa2).build();
				Endpoint end1 = Endpoint.newBuilder().setAddress(address1).build();
				Endpoint end2 = Endpoint.newBuilder().setAddress(address2).build();
			    LbEndpoint lb1 = LbEndpoint.newBuilder().setEndpoint(end1).build();
			    LbEndpoint lb2 = LbEndpoint.newBuilder().setEndpoint(end2).build();
				LocalityLbEndpoints llb = LocalityLbEndpoints.newBuilder().addLbEndpoints(lb1).addLbEndpoints(lb2).build();
				ClusterLoadAssignment cla = ClusterLoadAssignment.newBuilder()
						.setClusterName("user-service")
						.addEndpoints(llb).build();
				final DiscoveryResponse dr = DiscoveryResponse.newBuilder().setVersionInfo("1.0.0")
						.addResources(Any.pack(cla))
						.build();
				
				System.out.println("onCompleted");
				responseObserver.onNext(dr);
                responseObserver.onCompleted();
			}
		
	};
    return so;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【uni-app报错】获取用户收货地址uni.chooseAddress()报错问题

chooseAddress:fail the api need to be declared in …e requiredPrivateInf 原因&#xff1a; 小程序配置 / 全局配置 (qq.com) 解决&#xff1a; 登录小程序后台申请接口 按照流程申请即可 在项目根目录中找到 manifest.json 文件&#xff0c;在左侧导航栏选择源码视图&a…

Springboot整合Mybatis调用Oracle存储过程

1、配置说明 Oracel11g+springboot2.7.14+mybatis3.5.13 目标:springboot整合mybatis访问oracle中的存储过程,存储过程返回游标信息。 mybatis调用oracle中的存储过程方式 2、工程结构 3、具体实现 3.1、在Oracle中创建测试数据库表 具体数据可自行添加 create table s…

SIP网络音频模块-sip网络对讲音频模块(提供POE受电模块接口)

SIP网络音频模块-sip网络对讲音频模块&#xff08;提供POE受电模块接口&#xff09; SIP网络音频模块SV-2401V网络对讲音频模块&#xff08;支持POE&#xff09; SV-2403V网络对讲音频模块_网络语音对讲模块 网络音频模块 双向对讲 SIP广播系统 SIP网络音频模块嵌入式网络对…

YOLOv8改进后效果

数据集 自建铁路障碍数据集-包含路障&#xff0c;人等少数标签。其中百分之八十作为训练集&#xff0c;百分之二十作为测试集 第一次部署 版本&#xff1a;YOLOv5 训练50epoch后精度可达0.94 mAP可达0.95.此时未包含任何改进操作 第二次部署 版本&#xff1a;YOLOv8改进版本 首…

Mongodb基础操作

一、简介 MongoDB是一个NoSQL型的数据库&#xff0c;基于分布式文档型储存数据库&#xff0c;由C语言编写&#xff0c;它的特点是开源、高性能、高可用、高扩展、易部署。支持 Golang、RUBY、PYTHON、JAVA、C、PHP等多种开发语言。 二、应用场景 MongoDB适用于高并发读写、数据…

创新零售,京东重新答题?

继新一轮组织架构调整后&#xff0c;京东从低价到下沉动作不断。 新成立的创新零售部在京东老将闫小兵的带领下悄然完成了整合。近日&#xff0c;京喜拼拼已改名为京东拼拼&#xff0c;与七鲜、前置仓等业务共同承载起京东线上线下加速融合的梦想。 同时&#xff0c;拼拼的更…

FPGA: RS译码仿真过程

FPGA: RS译码仿真过程 在上一篇中记录了在FPGA中利用RS编码IP核完成信道编码的仿真过程&#xff0c;这篇记录利用译码IP核进行RS解码的仿真过程&#xff0c;带有程序和结果。 1. 开始准备 在进行解码的过程时&#xff0c;同时利用上一篇中的MATLAB仿真程序和编码过程&#x…

微信小程序|自定义弹窗组件

目录 引言小程序的流行和重要性自定义弹出组件作为提升用户体验和界面交互的有效方式什么是自定义弹出组件自定义弹出组件的概念弹出层组件在小程序中的作用和优势为什么需要自定义弹出组件现有的标准弹窗组件的局限性自定义弹出组件在解决这些问题上的优势最佳实践和注意事

日常BUG——Java使用Bigdecimal类型报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 直接上代码&#xff1a; Test public void test22() throws ParseException {System.out.p…

Linux怎样处理网络请求——彻底理解IO多路复用

常见的网络IO模型 网络 IO 模型分为四种&#xff1a;同步阻塞 IO、同步非阻塞IO、IO 多路复用、异步非阻塞 IO(Async IO, AIO)&#xff0c;其中AIO为异步IO&#xff0c;其他都是同步IO 同步阻塞IO 同步阻塞IO&#xff1a;在线程处理过程中&#xff0c;如果涉及到IO操作&…

这场大学生竞赛中,上百支队伍与合合信息用AI共克难题

目录 0 校企联合共克难题1 北京林业大学&#xff1a;文档格式转换2 浙江中医药大学&#xff1a;个性化题库3 中南林业科技大学&#xff1a;交互场景挖掘4 重庆邮电大学&#xff1a;大模型赋能智能文档5 总结 0 校企联合共克难题 近日&#xff0c;中国大学生服务外包创新创业大…

web前端开发基础入门html5+css3+js学习笔记(一)

目录 1.第一个前端程序2.前端工具的选择与安装3.VSCode开发者工具快捷键4.HTML5简介与基础骨架4.1 HTML5的DOCTYPE声明4.2 HTML5基本骨架4.2.1 html标签4.2.2 head标签4.2.3 body标签4.2.4 title标签4.2.5 meta标签 5.标签之标题5.1 快捷键5.1 标题标签位置摆放 6.标签之段落、…

GDP药品供应管理规范确保冷链运输合规性

药品运输面临许多挑战&#xff0c;包括产品可能因暴露在不利条件下导致降解。药品供应管理规范 (GDP) 运输指南在确保整个运输链的冷链合规性方面发挥着关键作用。 药品的分销与生产和制造生产线一样精细和敏感。自全球物流公司成立以来&#xff0c;配送过程中对受控环境的需求…

AI Chat 设计模式:14. 适配器模式

本文是该系列的第十四篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 关于适配器模式&#xff0c;如果由浅入深的来考察&#xff0c;你会依次提出什么问题…

【腾讯云 Cloud Studio 实战训练营】Hexo 框架 Butterfly 主题搭建个人博客

什么是Cloud Studio Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能在线编程。 ​ Hexo 博客成品展示 本人博客如下&…

根据一棵树的两种遍历构造二叉树

题目 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,…

sql server安装报错 合成活动模板库(ATL) 失败

错误 “合成活动模板库(ATL) 规则失败“ 解决办法&#xff1a; 进入SQL Server 2008R2安装包目录找到文件&#xff1a;sqlsupport_msi&#xff0c;安装此文件之后&#xff0c;再安装SQL Server&#xff0c;便可解决该问题。C:\SQL Server 2008R2\new\SQL Server 2008R2\2052_CH…

uniapp开发微信小程序使用painter将页面转换为图片并保存到本地相册

引言 我使用到painter的原因是&#xff0c;在uniapp开发微信小程序时&#xff0c;需要将一个页面的内容转换成图片保存到本地相册。 起初在网上找到很多都是在uniapp中使用 html2canvas 将网页转换成图片再jspdf将图片转换为pdf&#xff0c;但是这种方式在小程序环境不支持&am…

【数据结构】二叉树链式结构的实现及其常见操作

目录 1.手搓二叉树 2.二叉树的遍历 2.1前序、中序以及后序遍历 2.2二叉树的层序遍历 3.二叉树的常见操作 3.1求二叉树节点数量 3.2求二叉树叶子节点数量 3.3求二叉树第k层节点个数 3.3求二叉树的深度 3.4二叉树查找值为x的节点 4.二叉树的销毁 1.手搓二叉树 在学习…

[数据集][目标检测]钢材表面缺陷目标检测数据集VOC格式2279张10类别

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;2279 标注数量(xml文件个数)&#xff1a;2279 标注类别数&#xff1a;10 标注类别名称:["yueyawan",&…