首页 > 其他分享 >Spring Cloud Gateway 实现 gRpc 代理

Spring Cloud Gateway 实现 gRpc 代理

时间:2024-07-31 21:29:58浏览次数:15  
标签:netty 证书 gRpc Spring server grpc 报错 io Gateway

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 的报错:

6月 28, 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 格式的证书即可修复.

参考资料

标签:netty,证书,gRpc,Spring,server,grpc,报错,io,Gateway
From: https://www.cnblogs.com/ghimi/p/18335537

相关文章

  • Javaweb项目|基于SpringBoot的企业客户管理系统的设计与实现【源码+论文+PPT+部署视频
    我们提供多元化的技术项目服务,涵盖Java、PHP、Python等编程语言,以及前端开发、人工智能、大数据、单片机开发、ASP.NET、物联网等领域。我们还提供简历模板、面试题库和学习资料,帮助用户提升技术能力和就业竞争力。我们的服务内容包括:免费功能设计、任务书和开题报告撰写、中......
  • Javaweb项目|springboot基于JavaWeb技术的在线考试系统设计与实现【源码+论文+PPT+部
    我们提供多元化的技术项目服务,涵盖Java、PHP、Python等编程语言,以及前端开发、人工智能、大数据、单片机开发、ASP.NET、物联网等领域。我们还提供简历模板、面试题库和学习资料,帮助用户提升技术能力和就业竞争力。我们的服务内容包括:免费功能设计、任务书和开题报告撰写、中......
  • Javaweb项目|基于SpringBoot的企业客户管理系统的设计与实现
    收藏点赞不迷路 关注作者有好处文末获取源码一、系统展示二、万字文档展示 基于基于SpringBoot的企业客户管理系统的设计与实现开发语言:Java数据库:MySQL技术:Spring+SpringMVC+MyBatis+Vue工具:IDEA/Ecilpse、Navicat、Maven 编号:springboot024一、系统展示二......
  • Javaweb项目|springboot基于JavaWeb技术的在线考试系统设计与实现
    收藏点赞不迷路 关注作者有好处文末获取源码一、系统展示二、万字文档展示 基于springboot基于JavaWeb技术的在线考试系统设计与实现开发语言:Java数据库:MySQL技术:Spring+SpringMVC+MyBatis+Vue工具:IDEA/Ecilpse、Navicat、Maven 编号:springboot072一、系统展......
  • 57_Redis与Springboot的集合应用
    前提要实现,使用Redis存储登录状态需要一个完整的前端后端的项目前端项目搭建解压脚手架安装依赖配置请求代理选做:禁用EsLint语法检查VueAdminTemplate关闭eslint校验,lintOnSave:false设置无效解决办法_lintonsave:false-CSDN博客后端项目搭建创建springboot项......
  • 基于SpringBoot+Vue+uniapp的网上花店设计(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • springboot自学(5)自定义starter
      测试文件可以删除掉了,配置文件改一下后缀修改pom业务代码开发添加自动配置类,并且加上spring.factories到此为止就初步完成了,install到本地的maven仓库然后在使用的项目里加上依赖就行了导入项目,并调用定时任务报表开发先做个表格的打印方法表格......
  • 计算机Java项目|基于SpringBoot的科研工作量管理系统
    作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待与各位高校教师、企业......
  • 基于SpringBoot的图书电子商务网站设计与实现【源码+LW+U部署讲解】
    作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待与各位高校教师、企业......
  • springboot vue寝室小卖部系统源码和答辩PPT论文
    本文首先实现了“一分钟”寝室小卖部系统设计与实现管理技术的发展随后依照传统的软件开发流程,最先为系统挑选适用的言语和软件开发平台,依据需求分析开展控制模块制做和数据库查询构造设计,随后依据系统整体功能模块的设计,制作系统的功能模块图、E-R图。随后,设计框架,依据设计......