【笔记】Spring Cloud Gateway 实现 gRPC 代理

news2024/10/6 4:10:22

Spring Cloud Gateway 在 3.1.x 版本中增加了针对 gRPC 的网关代理功能支持,本片文章描述一下如何实现相关支持.本文主要基于 Spring Cloud Gateway 的 官方文档 进行一个实践练习。有兴趣的可以翻看官方文档。

在这里插入图片描述
由于 Grpc 是基于 HTTP2 协议进行传输的,因此 Srping Cloud Gateway 在支持了 HTTP2 的基础上天然支持对 Grpc 服务器的代理,只需要在现有代理基础上针对 grpc 协议进行一些处理即可。

以下为实现步骤,这里提供了示例代码,可以按需取用.

生成服务器证书

由于 Grpc 协议使用了 Http2 作为通信协议, Http2 在正常情况下是基于 TLS 层上进行通信的,这就要求我们需要配置服务器证书,这里为了测试,使用脚本生成了一套 CA 证书:

#!/bin/bash
# 指定生成证书的目录
dir=$(dirname "$0")/../resources/x509
[ -d "$dir" ] && find "$dir" -type f -exec rm -rf {} \;
mkdir -p "$dir"
pushd "$dir" || exit
# 生成.key  私钥文件 和 csr 证书签名请求文件
openssl req -new -nodes -sha256 -newkey rsa:2048 -keyout ca.key -out ca.csr \
-subj "/C=CN/ST=Zhejiang/L=Hangzhou/O=Ghimi Technology/OU=Ghimi Cloud/CN=ghimi.top"
# 生成自签名 .crt 证书文件
openssl x509 -req -in ca.csr -key ca.key -out ca.crt -days 3650
# 生成服务器私钥文件 和 csr 证书请求文件(私钥签名文件)
openssl req -new -nodes -sha256 -newkey rsa:2048 -keyout server.key -out server.csr \
-subj "/C=CN/ST=Zhejiang/L=Hangzhou/O=Ghimi Technology/OU=Ghimi Blog/CN=blog.ghimi.top"
# 3. 生成 server 证书,由 ca证书颁发
openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extensions SAN \
-extfile <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:dns.ghimi.top,IP:127.0.0.1,IP:::1"))
# 将 crt 证书转换为 pkcs12 格式,生成 server.p12 文件,密码 123456
openssl pkcs12 -export -in server.crt -inkey server.key -CAfile ca.crt \
-password pass:123456 -name server -out server.p12
# 导出服务器证书和证书私钥为 java keystore 格式 server.jks 为最终的导出结果 密码 123456
keytool -importkeystore -srckeystore server.p12 -destkeystore server.jks \
-srcstoretype pkcs12 -deststoretype jks -srcalias server -destalias server \
-deststorepass 123456 -srcstorepass 123456
# 将 ca 证书导入到 server.jks 中
keytool -importcert -keystore server.jks -file ca.crt -alias ca -storepass 123456 -noprompt
popd || exit

构建 Grpc 服务

首先我们需要创建一个 Maven 工程,并编写 gRPC 相关的服务器代码:

添加 gRPC 所需要的相关依赖:

<!-- grpc 关键依赖-->
io.grpc:grpc-netty-shaded:jar:1.64.0:runtime -- module io.grpc.netty.shaded [auto]
io.grpc:grpc-protobuf:jar:1.64.0:compile -- module io.grpc.protobuf [auto]
io.grpc:grpc-stub:jar:1.64.0:compile -- module io.grpc.stub [auto]
io.grpc:grpc-netty:jar:1.64.0:compile -- module io.grpc.netty [auto]

用 protobuf 生成一个 Java gRPC模板:

syntax = "proto3";
option java_multiple_files = true;
option java_package = "service";

message HelloReq {
  string name = 1;
}
message HelloResp {
  string greeting = 1;
}
service HelloService {
  rpc hello(HelloReq) returns (HelloResp);
}

然后在 pom.xml 添加 prptobuf 生成插件:

<!-- project.build.plugins -->
<plugin>
	<groupId>org.xolstice.maven.plugins</groupId>
	<artifactId>protobuf-maven-plugin</artifactId>
	<version>0.6.1</version>
	<configuration>
		<protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact>
		<pluginId>grpc-java</pluginId>
		<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.64.0:exe:${os.detected.classifier}</pluginArtifact>
		<!--设置grpc生成代码到指定路径-->
        <!--<outputDirectory>${project.build.sourceDirectory}</outputDirectory>-->
		<!--生成代码前是否清空目录-->
		<clearOutputDirectory>true</clearOutputDirectory>
	</configuration>
	<executions>
		<execution>
			<goals>
				<goal>compile</goal>
				<goal>compile-custom</goal>
			</goals>
		</execution>
	</executions>
</plugin>

注意在指定 protoc 的版本时要和上面 grpc 依赖的 protobuf 版本保持一致,否则可能会出现类找不到的报错。
在这里插入图片描述
然后执行执行 Maven 命令生成 Protobuf 对应的 Java 代码:

mvn protobuf:compile protobuf:compile-custom

之后就可以基于生成的 Protobuf Java 代码编写一个 gRPC Server 了 :

public static void main(String[] args) throws IOException, InterruptedException {
    TlsServerCredentials.Builder tlsBuilder = TlsServerCredentials.newBuilder();
    File serverCert = new ClassPathResource("/x509/server.crt").getFile();
    File serverKey = new ClassPathResource("/x509/server.key").getFile();
    File caCert = new ClassPathResource("/x509/ca.crt").getFile();
    ServerCredentials credentials  = tlsBuilder.trustManager(caCert).keyManager(serverCert, serverKey).build();
    // ServerCredentials credentials = InsecureServerCredentials.create(); // 不建议使用,非常坑
    Server server = Grpc.newServerBuilderForPort(443, credentials).addService(new HelloImpl()).build();
    server.start().awaitTermination();
}

static class HelloImpl extends HelloServiceGrpc.HelloServiceImplBase {
    @Override
    public void hello(HelloReq request, StreamObserver<HelloResp> responseObserver) {
        String msg = "hello " + request.getName() + " from server";
        System.out.println("server received a req,reply: " + msg);
        HelloResp res = HelloResp.newBuilder().setGreeting(msg).build();
        responseObserver.onNext(res);
        responseObserver.onCompleted();
    }
}

尝试启动 GrpcServer ,检查端口是否已被监听,当前端口绑定在 443 上,这里 GrpcServer 的服务器证书一定要配置.

编写 GrpcClient 代码:

public static void main(String[] args) throws InterruptedException, IOException {
    // 当服务器配置了证书时需要指定 ca 证书
    TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder();
    File caCert = new ClassPathResource("/x509/ca.crt").getFile();
    ChannelCredentials credentials = tlsBuilder.trustManager(caCert).build();
    // 不做服务器证书验证时使用这个
    // ChannelCredentials credentials = InsecureChannelCredentials.create();
    ManagedChannelBuilder<?> builder = Grpc.newChannelBuilder("127.0.0.1:7443", credentials);
    ManagedChannel channel = builder.build();
    HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
    service.HelloReq.Builder reqBuilder = service.HelloReq.newBuilder();
    HelloResp resp = stub.hello(reqBuilder.setName("ghimi").build());
    System.out.printf("success greeting from server: %s", resp.getGreeting());
    channel.shutdown().awaitTermination(5, TimeUnit.MINUTES);
}

执行 GrpcClient,调用 443 端口的 GrpcServer 查看执行效果:
在这里插入图片描述
现在我们就可以开发 Spring Cloud Gateway 了,首先添加依赖,我这里添加了 spring-cloud-starter-gateway:3.1.9 版本(为了适配 Java8,已经升级 Java11 的可以提升至更高版本)。

org.springframework.cloud:spring-cloud-starter-gateway:3.1.9

编写 GrpcGateway 启动类:

@SpringBootApplication
public class GrpcGateway {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(GrpcGateway.class, args);
    }
}

先不做配置尝试运行一下,看下是否能够正常运行:
在这里插入图片描述
可以看到成功监听到了 8080 端口,这是 Spring Cloud Gateway 的默认监听端口,现在我们在 /src/main/resources/ 目录下添加 application.yml 配置,配置代理 grpc 端口:

server:
  port: 7443 #端口号
  http2:
    enabled: true
  ssl:
    enabled: true
    key-store: classpath:x509/server.p12
    key-store-password: 123456
    key-store-type: pkcs12
    key-alias: server
spring:
  application:
    name: scg_grpc
  cloud:
    gateway: #网关路由配置
      httpclient:
        ssl:
          use-insecure-trust-manager: true
#          trustedX509Certificates:
#            - classpath:x509/ca.crt
      routes:
        - id: user-grpc #路由 id,没有固定规则,但唯一,建议与服务名对应
          uri: https://[::1]:443 #匹配后提供服务的路由地址
          predicates:
            #以下是断言条件,必选全部符合条件
            - Path=/**               #断言,路径匹配 注意:Path 中 P 为大写
            - Header=Content-Type,application/grpc
          filters:
            - AddResponseHeader=X-Request-header, header-value

添加 application.yml 后,重启 Spring Cloud Gateway 尝试用 GrpcClient 调用 7443 代理端口,可以看到请求成功:
在这里插入图片描述

报错分析

GrpcServer 和 GrpcClient 如果都配置了 InsecureServerCredentials 的情况下, GrpcClient 可以直接调用 GrpcServer 成功:

GrpcServer

TlsServerCredentials.Builder tlsBuilder = TlsServerCredentials.newBuilder();
ServerCredentials credentials = InsecureServerCredentials.create(); // 配置通过 h2c(http2 clear text) 协议访问
Server server = Grpc.newServerBuilderForPort(443, credentials).addService(new HelloImpl()).build();
server.start().awaitTermination();

GrpcClient

ChannelCredentials credentials = InsecureChannelCredentials.create(); // 通过 h2c 协议访问 GrpcServer
tlsBuilder.requireFakeFeature();
ManagedChannelBuilder<?> builder = Grpc.newChannelBuilder("127.0.0.1:443", credentials);
ManagedChannel channel = builder.build();
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
service.HelloReq.Builder reqBuilder = service.HelloReq.newBuilder();
HelloResp resp = stub.hello(reqBuilder.setName("ghimi").build());
System.out.printf("success greeting from server: %s\n", resp.getGreeting());
channel.shutdown().awaitTermination(5, TimeUnit.MINUTES);

此时使用 GrpcClient 调用 GrpcServer ,可以调用成功:
在这里插入图片描述

但是,如果中间添加了 Spring Cloud Gateway 的话, Grpc Server 和 Grpc Client 就都不能使用 InsecureCredentials 了, Spring Cloud Gateway 在这种场景下无论与 client 还是和 server 通信都会由于不识别的协议格式而报错:
在这里插入图片描述

如果 GrpcServer 没有配置服务器证书而是使用了 InsecureServerCredentials.create() ,GrpcClient 虽然不使用证书访问能够直接验证成功,但是如果中间通过 GrpcGateway 的话这种机制就有可能出现问题,因为 GrpcGateway 与 GrpcServer 之间的通信是基于 Http2 的,而非 Grpc 特定的协议,在 GrpcServer 没有配置服务器证书的情况下处理的包可能会导致 GrpcGateway 无法识别,但是如果 GrpcServer 配置了证书后 GrpcGateway 就能够正常验证了。

GrpcServer 在没有配置证书的情况下通过 Srping Cloud Gateway 的方式进行代理,并且 Spring Cloud Gateway 的 spring.cloud.gateway.http-client.ssl.use-insecure-trust-manager=true 的场景下 GrpcClient 访问 Spring Cloud Gateway 会报错:

GrpcClient 报错信息:

Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN: HTTP status code 500
invalid content-type: application/json
headers: Metadata(:status=500,x-request-header=header-value,content-type=application/json,content-length=146)
DATA-----------------------------
{"timestamp":"2024-06-28T13:18:03.455+00:00","path":"/HelloService/hello","status":500,"error":"Internal Server Error","requestId":"31f8f577/1-8"}
	at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:268)
	at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:249)
	at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:167)
	at service.HelloServiceGrpc$HelloServiceBlockingStub.hello(HelloServiceGrpc.java:160)
	at com.example.GrpcClient.main(GrpcClient.java:30)

报错信息解析,GrpcClient 报错结果来自于 Spring Cloud Gateway ,返回结果为不识别的返回内容 invalid content-type: application/json 这是由于 Spring Cloud Gateway 返回了的报错信息是 application/json 格式的,但是 GrpcClient 通过 grpc 协议通信,因此会将错误格式错误直接返回而非正确解析错误信息.

GrpcGateway 报错信息:

io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 00001204000000000000037fffffff000400100000000600002000000004080000000000000f0001
	at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1313) ~[netty-handler-4.1.100.Final.jar:4.1.100.Final]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP POST "/HelloService/hello" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1313) ~[netty-handler-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1383) ~[netty-handler-4.1.100.Final.jar:4.1.100.Final]

这里就是的报错信息是 GrpcGateway 无法正确解析来自 GrpcServer 的 http2 的包信息而产生的报错.这是由于 GrpcGateway 与 GrpcServer 在 h2c(Http2 Clean Text) 协议上的通信格式存在差异,从而引发报错.

最后是来自 GrpcServer 的报错:

628, 2024 9:18:03 下午 io.grpc.netty.shaded.io.grpc.netty.NettyServerTransport notifyTerminated
信息: Transport failed
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 16030302650100026103036f6977c824c322105c600bd1db
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:109)
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:321)
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:247)
	at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:453)

这里就是 GrpcServer 与 GrpcGateway 通信过程由于协议包无法识别导致通信终止,从而引发报错.

报错场景2

在 Spring Cloud Gateway 的 application.yml 中同时配置了 use-insecure-trust-manager: truetrustedX509Certificates 导致的报错:

spring:
  cloud:
    gateway: #网关路由配置
      httpclient:
        ssl:
          use-insecure-trust-manager: true
          trustedX509Certificates:
            - classpath:x509/ca.crt

use-insecure-trust-manager: true 表示在于 GrpcServer 通信的过程中不会验证服务器证书,这样如果证书存在什么问题的情况下就不会引发报错了,但是在同时配置了 use-insecure-trust-manager: truetrustedX509Certificates 的情况下 use-insecure-trust-manager: true 选项是不生效的,Spring Cloud Gateway 会还是尝试通过配置的 ca 证书去验证服务器证书,从而引发报错,因此不要同时配置 use-insecure-trust-manager: truetrustedX509Certificates这两个选项。

# 同时配置了 `use-insecure-trust-manager: true` 和 `trustedX509Certificates` 后服务证书校验失败报错
javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address 0:0:0:0:0:0:0:1 found
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130) ~[na:na]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP POST "/HelloService/hello" [ExceptionHandlingWebHandler]
Original Stack Trace:

客户端通常情况下只需要配置 ca 证书,用于验证服务器证书,但是验证服务器证书这一步是可以跳过的,在一些场景下服务器证书的校验比较严格的时候容易出问题,此时可以选择不进行服务器证书校验,在 Spring Cloud Gateway 代理访问 GrpcServer 时,可以为 Spring Cloud Gateway 配置 use-insecure-trust-manager: true 来取消对 GrpcServer 的强验证。

No subject alternative names matching IP address 0:0:0:0:0:0:0:1 found

这个问题就是在校验服务器证书时,由于服务器证书校验失败导致的报错了,通常情况下, client 会校验服务器的FQDN域名信息是否与请求的连接一致:

# 请求服务器证书
openssl req -new -nodes -sha256 -newkey rsa:2048 -keyout server.key -out server.csr \
-subj "/C=CN/ST=Zhejiang/L=Hangzhou/O=Ghimi Technology/OU=Ghimi Blog/CN=blog.ghimi.top"

上面是在使用命令生成服务器证书时配置的信息,其中 CN=blog.ghimi.top 就是我配置的域名信息,这就要求我的 GrpcServer 的 ip 地址绑定了这个域名,然后 GrpcClient 通过这个域名访问:

ManagedChannelBuilder<?> builder = Grpc.newChannelBuilder("blog.ghimi.top:7443", credentials);

在这种情况下 GrpcClient 会拿服务器返回的证书与当前连接信息进行比较,如果一致则服务器验证成功,否则验证失败并抛出异常.

在 GrpcServer 只有 ip 地址没有域名的情况下,基于域名的验证就不生效了,此时去做证书验证就一定会报错:

# 同时配置了 `use-insecure-trust-manager: true` 和 `trustedX509Certificates` 后服务证书校验失败报错
javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address 0:0:0:0:0:0:0:1 found
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130) ~[na:na]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP POST "/HelloService/hello" [ExceptionHandlingWebHandler]
Original Stack Trace:

报错信息中提到的 subject alternative names 就是在域名失效后的另外一种验证手段,他要求ca在签发服务器证书时向服务器证书中添加一段附加信息,这个信息中可以添加证书的可信 ip 地址:

# 通过 ca 证书颁发服务器证书
openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extensions SAN \
-extfile <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:dns.ghimi.top,IP:127.0.0.1"))

上面脚本中的 IP:127.0.0.1 就是添加的可信地址,我们可以同时添加多个服务器地址,以上面的报错为例,我们只需要在生成服务器证书的时候添加 ::1 的本地 ipv6 地址即可修复该错误:

openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extensions SAN \
-extfile <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:dns.ghimi.top,IP:127.0.0.1,IP:::1"))

报错场景4 使用 pkcs12 配置了多张自签名 ca 证书识别失效问题

解决方案,改为使用 Java Keystore 格式的证书即可修复.

参考资料

  • Spring Cloud Gateway and gRPC
  • spring-cloud-gateway-grpc
  • gRPC-Spring-Boot-Starter 文档
  • rx-java
  • Working with Certificates and SSL
  • 介绍一下 X.509 数字证书中的扩展项 subjectAltName

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

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

相关文章

已解决java.security.acl.LastOwnerException:无法移除最后一个所有者的正确解决方法,亲测有效!!!

已解决java.security.acl.LastOwnerException&#xff1a;无法移除最后一个所有者的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 1. 检查当前所有者数量 2. 添加新的所有者 3. 维…

【Python系列】Python 中循环依赖问题及其解决方案

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

CAN和CANFD数据写入.asc文件的dll

因为工作需要&#xff0c;需要做一些硬件不是CANoe的上位机&#xff08;比如说周立功CAN,NI-CAN&#xff09;&#xff0c;上位机需要有记录数据的功能&#xff0c;所以用Qt制作了一个记录数据的dll&#xff0c;方便重复使用&#xff08;因为有的客户指定了编程软件&#xff0c;…

Spring框架整体概念

Spring框架基础概念 首先&#xff0c; 从Spring框架的整体架构和组成对整体框架有个认知。 Spring基础 - Spring和Spring框架组成 上图是从官网4.2.x获取的原图&#xff0c;目前我们使用最广法的版本应该都是5.x&#xff0c;5版本移除了Web模块中的Portlet子模块&#xff0c;新…

机器人控制系列教程之控制理论概述

经典控制理论 经典控制理论主要研究线性定常系统。所谓线性控制系统是指系统中各组成环节或元件的状态由线性微分方程描述的控制系统。如果描述该线性系统的微分方程系数是常数,则称为线性定常系统。描述自动控制系统输入量、输出量和内部量之间关系的数学表达式称为系统的数学…

STM32第七课:KQM6600空气质量传感器

文章目录 需求一、KQM6600模块及接线方法二、模块配置流程1.环境2.配置时钟和IO3.配置串口初始化&#xff0c;使能以及中断4.中断函数 三、数据处理四、关键代码总结 需求 能够在串口实时显示当前的VOC&#xff08;挥发性有机化合物&#xff09;&#xff0c;甲醛和Co2浓度。 …

GPU算力是什么,哪些行业需要用到GPU算力?

近两年&#xff0c;计算能力已成为推动各行各业发展的关键因素。而GPU&#xff08;图形处理器&#xff09;算力&#xff0c;作为现代计算技术的重要分支&#xff0c;正逐渐在多个领域展现出其强大的潜力和价值。尚云将简要介绍GPU算力的定义和基本原理&#xff0c;并探讨其在哪…

使用Apache POI库在Java中导出Excel文件的详细步骤

使用Apache POI库在Java中导出Excel文件的详细步骤 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技…

企业中对RAG的优化方案

企业中对RAG的优化方案 RAG优化&#xff1a;检索、语义和生成方面的提升RAG流程一、数据处理优化数据清洗实际案例 二、检索方面优化向量库检索倒排索引数据库检索 三、生成方面优化调整Prompt 四、架构优化RAGAgent架构Self-RAG架构Agentic RAG架构 总结 RAG优化&#xff1a;检…

【Django】网上蛋糕项目商城-热销和新品

概念 本文将完成实现项目的热销和新品两个分类的商品列表进行分页展示。 热销和新品功能实现步骤 在head.html头部页面中点击这两个超链接向服务器发送请求。 在urls.py文件中定义该请求地址 path(goodsrecommend_list/,views.goodsrecommend_list) 在views.py文件中定义g…

mac配置hdc

首先需要找到 .zshrc 文件&#xff1a; 访达进入到user文件夹中&#xff0c;shiftcommand.键显示隐藏文件&#xff1a; 双击打开进行编辑&#xff0c;在最后添加 //HDC_HOME 指的是hdc的绝对路径&#xff0c;要替换成自己的路径 export HDC_HOME/Users/你的名字/Library/Huaw…

系统架构设计师 - 计算机网络(1)

计算机网络 计算机网络TCP/IP 协议簇TCP与UDP ★★★DHCP与DNS ★★★DNS 协议应用DHCP 协议应用 网络规划与设计逻辑设计与物理设计 ★★★★逻辑网络设计物理网路设计 层次化网络设计网络冗余设计 网络存储 ★★网络存储方式磁盘阵列 - Raid 大家好呀&#xff01;我是小笙&am…

二刷算法训练营Day45 | 动态规划(7/17)

目录 详细布置&#xff1a; 1. 139. 单词拆分 2. 多重背包理论基础 3. 背包总结 3.1 背包递推公式 3.2 遍历顺序 01背包 完全背包 详细布置&#xff1a; 1. 139. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单…

北大西洋横断面(ANT)项目计划

North Atlantic Transect (ANT) program 简介 - **&#x1f5fa;️ 北大西洋横断面计划 (ANT) 概述** 北大西洋横断面计划 (ANT) 是一个由美国资助的 GEOTRACES 项目&#xff0c;旨在测量非洲海岸沿线的海洋化学参数。该计划收集了有关海水中的溶解氧、营养盐、碳酸盐离子、微…

STM32第八课:Su-03t语音识别模块

文章目录 需求一、SU03T语音识别模块二、模块配置流程1.固件烧录2.配置串口和传输引脚3.中断函数4.double类型转换5 数据发送6.接收处理 三、该模块完整代码总结 需求 基于上次完成空气质量传感器&#xff0c;利用SU03T语音识别模块&#xff0c;实现空气质量的语音问答播报。 …

240622_昇思学习打卡-Day4-ResNet50迁移学习

240622_昇思学习打卡-Day4-ResNet50迁移学习 我们对事物的认知都是一点一点积累出来的&#xff0c;往往借助已经认识过的东西&#xff0c;可以更好地理解和认识新的有关联的东西。比如一个人会骑自行车&#xff0c;我们让他去骑摩托车他也很快就能学会&#xff0c;比如已经学会…

STM32开发方式的演变与未来展望

一、STM32开发方式的演变 自2007年STM32微控制器首次亮相以来&#xff0c;其开发方式经历了从寄存器到标准库&#xff0c;再到HAL&#xff08;硬件抽象层&#xff09;的演变。 1.寄存器开发&#xff08;2007年-2010年代初&#xff09; 最初&#xff0c;由于初期缺乏足够的软…

Cyuyanzhong的内存函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、memcpy函数的使用与模拟实现二、memmove函数的使用和模拟实现三、memset函数与memcmp函数的使用&#xff08;一&#xff09;、memset函数&#xff08;内存块…

Qt creator实现一个简单计算器

目录 1 界面设计 2 思路简介 3 代码 目录 1 界面设计 ​2 思路简介 3 代码 3.1 widget.h 3.2 widget.c 4 完整代码 在这里主要记载了如何使用Qt creator完成一个计算器的功能。该计算器可以实现正常的加减乘除以及括号操作&#xff0c;能实现简单的计算器功能。 1 界…

.NET使用CsvHelper快速读取和写入CSV文件

前言 在日常开发中使用CSV文件进行数据导入和导出、数据交换是非常常见的需求&#xff0c;今天我们来讲讲在.NET中如何使用CsvHelper这个开源库快速实现CSV文件读取和写入。 CsvHelper类库介绍 CsvHelper是一个.NET开源、快速、灵活、高度可配置、易于使用的用于读取和写入C…