好,我们以你的 euclidolap.proto
文件为例,调整代码结构,让服务逻辑更清晰,同时将 euclidolap
模块分离到独立文件中。
假设文件结构调整
我们将 euclidolap.proto
生成的代码放到 src/euclidolap
模块中,同时将服务端逻辑分开组织。
最终文件结构如下:
project/
├── build.rs # Protobuf 代码生成脚本
├── Cargo.toml # Rust 项目配置
├── proto/
│ └── euclidolap.proto # Protobuf 接口定义文件
├── src/
│ ├── euclidolap/
│ │ ├── mod.rs # euclidolap 模块主入口
│ │ └── euclidolap.rs # 自动生成的 gRPC 代码
│ ├── main.rs # 主程序文件
1. build.rs
文件
确保 build.rs
将 euclidolap.proto
的代码生成到 src/euclidolap/euclidolap.rs
:
fn main() {
let out_dir = "src/euclidolap"; // 指定代码生成目录
if let Err(e) = tonic_build::configure()
.out_dir(out_dir) // 输出到 euclidolap 模块目录
.compile(&["proto/euclidolap.proto"], &["proto"]) // 编译 .proto 文件
{
eprintln!("Failed to compile proto files: {}", e);
std::process::exit(1);
}
// 通知 Cargo 监控 .proto 文件的变化
println!("cargo:rerun-if-changed=proto/euclidolap.proto");
}
这段代码会将生成的 euclidolap.rs
放在 src/euclidolap/
目录下。
2. src/euclidolap/mod.rs
文件
mod.rs
是 euclidolap
模块的主入口,内容如下:
// 引用自动生成的 gRPC 代码
pub mod euclidolap {
include!("euclidolap/euclidolap.rs");
}
这里通过 include!
将自动生成的 euclidolap.rs
文件引入模块中。
3. src/main.rs
文件
主文件 main.rs
使用 euclidolap
模块实现服务逻辑:
mod euclidolap;
use euclidolap::euclidolap::olap_service_server::{OLAPService, OLAPServiceServer};
use euclidolap::euclidolap::{OLAPRequest, OLAPResponse, Row};
use tonic::{transport::Server, Request, Response, Status};
// 自定义服务实现
#[derive(Debug, Default)]
pub struct MyOLAPService {}
#[tonic::async_trait]
impl OLAPService for MyOLAPService {
async fn execute_operation(
&self,
request: Request<OLAPRequest>, // 客户端的请求
) -> Result<Response<OLAPResponse>, Status> {
println!("Received request: {:?}", request);
// 从请求中解析操作类型和语句
let operation_type = request.into_inner().operation_type;
let statement = request.into_inner().statement;
println!("Operation Type: {}, Statement: {}", operation_type, statement);
// 伪造一个响应,返回结果
let response = OLAPResponse {
rows: vec![Row {
columns: vec!["Result 1".to_string(), "Result 2".to_string()],
}],
};
Ok(Response::new(response))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "0.0.0.0:50052".parse().unwrap();
let olap_service = MyOLAPService::default();
println!(">>> EuclidOLAP Server is listening on {} <<<", addr);
Server::builder()
.add_service(OLAPServiceServer::new(olap_service)) // 注册 OLAP 服务
.serve(addr)
.await?;
Ok(())
}
代码逻辑
-
MyOLAPService
服务实现:- 实现了
OLAPService
接口中的execute_operation
方法。 - 解析客户端请求,返回一个伪造的响应数据(包含两列结果)。
- 实现了
-
服务端启动:
- 服务监听
0.0.0.0:50052
,通过OLAPServiceServer::new
注册服务。
- 服务监听
4. euclidolap.proto
示例
为了匹配代码示例,假设你的 euclidolap.proto
文件内容如下:
syntax = "proto3";
package euclidolap;
service OLAPService {
rpc ExecuteOperation(OLAPRequest) returns (OLAPResponse);
}
message OLAPRequest {
string operation_type = 1; // 操作类型
string statement = 2; // 多维查询语句
}
message OLAPResponse {
repeated Row rows = 1; // 返回的结果行
}
message Row {
repeated string columns = 1; // 每行的列数据
}
代码生成后会包含什么
build.rs
会根据 euclidolap.proto
文件生成如下内容到 src/euclidolap/euclidolap.rs
:
- Rust 结构体对应于
.proto
中的message
。 - Rust Trait 用于实现 gRPC 服务接口,例如
OLAPServiceServer
。
生成的代码会自动处理序列化和反序列化逻辑,简化服务实现。
运行流程
-
启动服务:
- 执行
cargo run
启动服务端,监听0.0.0.0:50052
。
- 执行
-
客户端请求:
- 客户端发送
OLAPRequest
请求,其中包含operation_type
和statement
。
- 客户端发送
-
服务端处理:
- 服务端解析请求,执行
execute_operation
方法。 - 返回构造的
OLAPResponse
,其中包含伪造的结果行。
- 服务端解析请求,执行
-
响应客户端:
- 客户端接收到
OLAPResponse
,完成请求。
- 客户端接收到
总结
通过将 euclidolap
模块分离到单独的文件夹和模块中,你的代码结构更清晰且易于扩展。结合 build.rs
和 Tonic
的生成逻辑,服务端的实现变得高效而简洁。