分布微服务电商订单系统Rust编码开发[上] - 指南
1 整体设计与项目构建
1.1 系统架构设计
A. 微服务划分
订单服务:处理订单创建、查询、确认、状态更新;
支付服务:模拟支付流程,回调订单状态;
库存服务:管理商品库存,支持扣减/回滚;
统一网关:路由请求、托管前端页面(HTML/CSS/JS);
服务注册发现:Nacos 管理服务实例;
素材存储:MongoDB 存储各服务信息。
B. 技术栈
框架:Actix-web(HTTP 服务);
数据库驱动:mongodb(官方库);
服务注册:reqwest(HTTP 客户端调用 Nacos API);
异步运行时:Tokio;
配置管理:dotenv。
1.2 项目架构及其主要编码获取
采用“腾迅ima”,借助AI大模型hunyuan/deepSeek,获取项目设计框架和主要程序初始编码,如图1所示。
图1 “腾迅ima”获取“项目设计框架和主要程序初始编码”截图
1.3 主项目及其各个子项目构建
RustRoverIDE下创建主任务ecommerce-uservices,再在该目录下分别创建四个子项目:order_service、payment_service、inventory_service、gateway。在主项目的Cargo.toml记录中配置整体Workspace如下文本框所示。
[workspace]
resolver = "2"
members = ["order_service", "payment_service", "inventory_service", "gateway"]
1.4 项目文件目录结构
ecommerce-uservices/
├── gateway/ # 统一网关服务
│ ├── src/
│ │ ├── main.rs # 网关入口
│ │ └── routes.rs # 路由配置
│ ├── templates # HTML页面模板
│ │ ├── index.html # 主页面
│ │ ├── orders.html # 订单页面
│ │ ├── payment.html # 支付页面
│ │ └── inventory.html # 库存页面
│ ├─ static/ # 静态页面定义
│ │ ├── style.css # 全局样式
│ │ └── script.js # 全局脚本
│ └── Cargo.toml
├── order_service/ # 订单服务
│ ├── src/
│ │ ├── main.rs # 服务入口
│ │ ├── handler.rs # HTTP 请求处理
│ │ ├── model.rs # 数据结构(订单实体)
│ │ └── db.rs # MongoDB 操作
│ └── Cargo.toml
├── payment_service/ # 支付服务(类似订单服务结构)
│ ├── src/
│ │ ├── main.rs # 服务入口
│ │ ├── handler.rs # HTTP 请求处理
│ │ ├── model.rs # 数据结构(订单实体)
│ │ └── db.rs # MongoDB 操作
│ └── Cargo.toml
├── inventory_service/ # 库存服务(类似订单服务结构)
│ ├── src/
│ │ ├── main.rs # 服务入口
│ │ ├── handler.rs # HTTP 请求处理
│ │ ├── model.rs # 数据结构(订单实体)
│ │ └── db.rs # MongoDB 执行
│ └── Cargo.toml
└── Cargo.toml # Workspace 配置文件
2 订单服务编码设计
2.1 架构原则
- 分层设计:业务逻辑(handler)、数据访问(db)、模型(model)分离。
- 异步非阻塞:基于Tokio运行时,高并发下性能优异。
- 安全序列化:Serde确保JSON与结构体转换的类型安全。
2.2 订单微服务实现功能
- 订单创建:依据POST /orders接口创建新订单
- 订单查询:通过GET /orders/{order_id}接口查询订单详情
- 订单确认:通过PUT /orders/{order_id}/confirm接口确认订单
- 订单刷新:利用PUT /orders/{order_id}/refresh接口更新订单时间戳
2.3 main.rs--服务入口与路由配置
初始化数据库连接池,配置路由(创建订单、查询订单、确认订单、刷新订单)。
modmodel;moddb;modhandler;usestd::collections::HashMap;
usenacos_sdk::api::naming::{NamingService,NamingServiceBuilder,ServiceInstance};
usenacos_sdk::api::props::ClientProps;usehandler::order_routes;
usestd::net::SocketAddr;uselog::info;
#[tokio::main]
asyncfnmain()->Result<(),Box<dynstd::error::Error>>{
// 初始化日志
env_logger::init();info!("Starting order service");
// 服务配置
letservice_name="order_service".to_string();letgroup="ecommerce".to_string();
letip="127.0.0.1";letport=9001;
// 创建Nacos客户端
letclient=NamingServiceBuilder::new(
ClientProps::new().server_addr("127.0.0.1:8848").namespace("public"),
).build()?;
// 构建服务实例(修正字段类型和命名)
letinstance=ServiceInstance{service_name:Some(service_name.clone()),
ip:ip.to_string(),port,weight:1.0,healthy:true,enabled:true,
ephemeral:true,instance_id:None,cluster_name:Some("DEFAULT".to_string()),
metadata:HashMap::from([("version".into(),"1.0".into()),("service".into(),"inventory".into())])
};
// 注册服务(修正参数传递)
client.register_instance(service_name.clone(),Some(group.clone()),instance.clone()).await?;
println!("✅库存服务注册成功");
/* 服务注销逻辑(参数匹配)
client.deregister_instance( service_name, Some(group), instance ).await?; */
// 启动Warp服务器
letroutes=order_routes();
letaddr:SocketAddr="127.0.0.1:9001".parse().unwrap();
println!("Order service running on {}",addr);
warp::serve(routes).run(addr).await; Ok(())
}
2.4 model.rs--数据模型定义
usechrono::Utc;usemongodb::bson::oid::ObjectId;usemongodb::error::Error;
useserde::{Deserialize,Serialize};usewarp::reject::Reject;
#[derive(Debug,Serialize,Deserialize)]
pubstructOrder{
#[serde(rename="_id",skip_serializing_if="Option::is_none")]
pubid:Option<ObjectId>,puborder_id:String,pubuser_id:String,
pubproduct_id:String,pubquantity:i32,pubtotal_price:f64,
pubstatus:String,// "created", "confirmed", "completed", "canceled"
pubcreated_at:String,pubupdated_at:String,}
#[derive(Debug,Serialize,Deserialize)]
pubstructCreateOrderRequest{pubuser_id:String,
pubproduct_id:String,pubquantity:i32,pubtotal_price:f64,}
#[derive(Debug,Serialize,Deserialize)]
pubstructUpdateOrderStatusRequest{pubstatus:String,}
implOrder{
pubfnnew(req:CreateOrderRequest)->Self{
letnow=Utc::now().to_rfc3339();
Order{id:None,order_id:uuid::Uuid::new_v4().to_string(),
user_id:req.user_id,product_id:req.product_id,
quantity:req.quantity,total_price:req.total_price,
status:"created".to_string(),created_at:now.clone(),updated_at:now,
}
}
}
#[derive(Debug,Serialize,Deserialize)]
pubstructOrderError{message:String,}
implRejectforOrderError{}
implOrderError{
pubfnnew(message:&Error)->Self{
OrderError{message:message.to_string(),}
}
}
usewarp::{Filter,Rejection,Reply};useserde_json::json;
2.5 handler.rs--请求处理逻辑
处理HTTP请求,调用数据库操作并返回响应。
usecrate::model::{Order,CreateOrderRequest,UpdateOrderStatusRequest,OrderError};
usecrate::db::{create_order,get_order,update_order_status,refresh_order};
pubasyncfnhealth_check()->Result<implReply,Rejection>{
Ok(warp::reply::json(&json!({"status":"ok"})))
}
pubasyncfnhandle_create_order(req:CreateOrderRequest)->Result<implReply,Rejection>{
letorder=Order::new(req);
matchcreate_order(order).await{
Ok(id)=>Ok(warp::reply::json(&json!({"order_id":id}))),
Err(e)=>Err(warp::reject::custom(OrderError::new(&e))),
}
}
pubasyncfnhandle_get_order(order_id:String)->Result<implReply,Rejection>{
matchget_order(&order_id).await{
Ok(Some(order))=>Ok(warp::reply::json(&order)),
Ok(None)=>Err(warp::reject::not_found()),
Err(e)=>Err(warp::reject::custom(OrderError::new(&e))),
}
}
pubasyncfnhandle_confirm_order(order_id:String,req:UpdateOrderStatusRequest)->Result<implReply,Rejection>{
matchupdate_order_status(&order_id,&req.status).await{
Ok(_)=>Ok(warp::reply::json(&json!({"status":"confirmed"}))),
Err(e)=>Err(warp::reject::custom(OrderError::new(&e))),
}
}
pubasyncfnhandle_refresh_order(order_id:String)->Result<implReply,Rejection>{
matchrefresh_order(&order_id).await{
Ok(_)=>Ok(warp::reply::json(&json!({"status":"refreshed"}))),
Err(e)=>Err(warp::reject::custom(OrderError::new(&e))),
}
}
pubfnorder_routes()->implFilter<Extract=implReply,Error=Rejection>+Clone{
lethealth=warp::path!("health").and(warp::get()).and_then(health_check);
letcreate_order=warp::path!("orders").and(warp::post())
.and(warp::body::json()).and_then(handle_create_order);
letget_order=warp::path!("orders"/String).and(warp::get()).and_then(handle_get_order);
letconfirm_order=warp::path!("orders"/String/"confirm")
.and(warp::put()).and(warp::body::json()).and_then(handle_confirm_order);
letrefresh_order=warp::path!("orders"/String/"refresh")
.and(warp::put()).and_then(handle_refresh_order);
health.or(create_order).or(get_order).or(confirm_order).or(refresh_order)
}
2.6 db.rs--数据库操作
usemongodb::{Client,Collection};usemongodb::options::ClientOptions;
usecrate::model::Order;usemongodb::bson::doc;usetokio::sync::OnceCellasAsyncOnceCell;
// 使用OnceCell来延迟初始化全局集合
staticORDER_COLLECTION:AsyncOnceCell<Collection<Order>>=AsyncOnceCell::const_new();
// 异步初始化MongoDB集合
asyncfninit_collection()->Collection<Order>{
letclient_options=ClientOptions::parse("mongodb://localhost:27017")
.await.expect("Failed to parse MongoDB connection string");
letclient=Client::with_options(client_options)
.expect("Failed to create MongoDB client");
client.database("ecommerce1").collection("orders")
}
// 获取集合实例
asyncfnget_collection()->&'staticCollection<Order>{
ORDER_COLLECTION.get_or_init(init_collection).await
}
pubasyncfncreate_order(order:Order)->Result<String,mongodb::error::Error>{
letcollection=get_collection().await;
letresult=collection.insert_one(order,None).await?;
Ok(result.inserted_id.to_string())
}
pubasyncfnget_order(order_id:&str)->Result<Option<Order>,mongodb::error::Error>{
letcollection=get_collection().await;
letfilter=doc!{"order_id":order_id};
collection.find_one(filter,None).await
}
pubasyncfnupdate_order_status(order_id:&str,status:&str)->Result<(),mongodb::error::Error>{
letcollection=get_collection().await;
letfilter=doc!{"order_id":order_id};
letupdate=doc!{"$set":{"status":status,"updated_at":chrono::Utc::now().to_rfc3339()}};
collection.update_one(filter,update,None).await?; Ok(())
}
pubasyncfnrefresh_order(order_id:&str)->Result<(),mongodb::error::Error>{
letcollection=get_collection().await;
letfilter=doc!{"order_id":order_id};
letupdate=doc!{"$set":{"updated_at":chrono::Utc::now().to_rfc3339()}};
collection.update_one(filter,update,None).await?; Ok(())
}
2.7 Cargo.toml--依赖配置
[package]
name = "order_service"
version= "0.1.0"
edition= "2024"
[dependencies]
tokio = { version= "1.0", features= ["full"] }
warp = "0.3"
serde = { version= "1.0", features= ["derive"] }
serde_json= "1.0"
mongodb= { version= "2.0", feature= ["sync"] }
lazy_static= "1.4"
chrono= { version= "0.4", features= ["serde"] }
uuid = { version= "0.8", features= ["v4"] }
env_logger= "0.11"
log = "0.4"
nacos-sdk= "0.4.0"
3 支付服务编码设计
3.1 功能设计
A. 支付流程
用户选择订单并指定支付方式(微信/支付宝);
系统模拟支付处理(80%成功率);
更新订单状态和支付状态。
B. 订单查询
帮助按订单ID查询单个订单;
支持按用户ID、支付状态等多条件查询订单列表;
C. NACOS服务注册
服务启动时自动注册到NACOS;
定期发送心跳维持服务健康状态。
D. MongoDB集成
使用官方MongoDB Rust驱动;
支持订单的CRUD操作;
拥护复杂查询和更新。
E. 错误处理
自定义错误类型;
统一的错误处理机制。
3.2 main.rs--服务入口与路由配置
modhandler;moddb;modmodel;
usewarp::{Filter};usestd::sync::Arc;usetokio::sync::Mutex;
usecrate::handler::payment_routes;usecrate::db::DbClient;
usenacos_sdk::api::naming::{NamingService,NamingServiceBuilder,ServiceInstance};
usenacos_sdk::api::props::ClientProps;usestd::collections::HashMap;
#[tokio::main]
asyncfnmain()->Result<(),Box<dynstd::error::Error>>{
// 服务配置
letservice_name="payment_service".to_string();
letgroup="ecommerce".to_string();letip="127.0.0.1";letport=9002;
// 创建Nacos客户端
letclient=NamingServiceBuilder::new(
ClientProps::new().server_addr("127.0.0.1:8848").namespace("public"),).build()?;
// 构建服务实例(修正字段类型和命名)
letinstance=ServiceInstance{service_name:Some(service_name.clone()),
ip:ip.to_string(),port,weight:1.0,healthy:true,enabled:true,
ephemeral:true,instance_id:None,cluster_name:Some("DEFAULT".to_string()),
metadata:HashMap::from([("version".into(),"1.0".into()),("service".into(),"inventory".into())])};
// 注册服务(修正参数传递)
client.register_instance(service_name.clone(),Some(group.clone()),instance.clone()).await?;
println!("✅库存服务注册成功");
/* 服务注销逻辑(参数匹配)
client.deregister_instance( service_name, Some(group), instance ).await?; */
// 初始化MongoDB客户端
letdb_client=matchinit_db_client().await{Ok(client)=>client,
Err(e)=>{eprintln!("Failed to initialize database client: {}",e);std::process::exit(1);}};
// 创建共享的数据库客户端
letdb=Arc::new(Mutex::new(db_client));
// 设置路由
letroutes=payment_routes(db).with(warp::cors().allow_any_origin()).with(warp::log("payment_service"));
println!("Payment service started on port 9002");
warp::serve(routes).run(([127,0,0,1],9002)).await;Ok(())
}
asyncfninit_db_client()->Result<DbClient,Box<dynstd::error::Error>>{
// MongoDB连接配置
leturi="mongodb://localhost:27017";
letdb_name="ecommerce1";//"payment_db";
letcollection_name="orders";
// 创建数据库客户端
letclient=DbClient::new(uri,db_name,collection_name).await?; Ok(client)
}
3.3 model.rs--数据模型定义
useserde::{Deserialize,Serialize};usemongodb::bson::oid::ObjectId;
#[derive(Debug,Serialize,Deserialize)]
pubenumCurrency{CNY,USD,EUR,}
#[derive(Debug,Serialize,Deserialize)]
pubenumPayStatus{Pending,Processing,Paid,Failed,Refunded,}// 待支付,处理中,已支付,支付失败,已退款
#[derive(Debug,Serialize,Deserialize)]
pubenumPaymentMethod{WeChatPay,Alipay,BankTransfer,CreditCard,}
#[derive(Debug,Serialize,Deserialize)]
pubstructPaymentRequest{puborder_id:String,pubpayment_method:PaymentMethod,pubcurrency:Currency,}
#[derive(Debug,Serialize,Deserialize)]
pubstructPaymentResponse{puborder_id:String,pubstatus:PayStatus,pubpayment_method:PaymentMethod,
pubamount:f64,pubtransaction_id:Option<String>,pubpayment_time:Option<String>,}
#[derive(Debug,Serialize,Deserialize)]
pubstructQueryOrderRequest{puborder_id:Option<String>,pubuser_id:Option<String>,
pubpay_status:Option<PayStatus>,}
#[derive(Debug,Serialize,Deserialize)]
pubstructOrder{
#[serde(rename="_id",skip_serializing_if="Option::is_none")]
pubid:Option<ObjectId>,
puborder_id:String,
pubuser_id:String,
pubproduct_id:String,
pubquantity:i32,
pubtotal_price:f64,
pubstatus:String,// "created", "confirmed", "completed", "canceled"
pubpay_status:PayStatus,
pubpayment_method:Option<PaymentMethod>,
pubcurrency:Option<Currency>,
pubtransaction_id:Option<String>,
pubcreated_at:String,
pubupdated_at:String,
}
implFrom<Order>forPaymentResponse{
fnfrom(order:Order)->Self{
PaymentResponse{order_id:order.order_id,status:order.pay_status,
payment_method:order.payment_method.unwrap_or(PaymentMethod::WeChatPay),
amount:order.total_price,transaction_id:order.transaction_id,
payment_time:Some(order.updated_at),
}
}
}
3.4 handler.rs--请求处理逻辑
处理HTTP请求,调用数据库操作并返回响应。
usewarp::{Filter,Rejection,Reply};useserde_json::json;usestd::sync::Arc;
usetokio::sync::Mutex;usethiserror::Error;userand::Rng;usecrate::db::{DbClient,DbError};
usecrate::model::{PaymentRequest,PaymentResponse,QueryOrderRequest,PayStatus};
#[derive(Error,Debug)]
pubenumHandlerError{
#[error("Database error: {0}")]
DbError(#[from]DbError),
#[error("Payment processing error")]
PaymentError,
#[error("Order not found")]
OrderNotFound,
}
implwarp::reject::RejectforHandlerError{}
pubasyncfnhandle_payment(order_id:String,req:PaymentRequest,
db:Arc<Mutex<DbClient>>,)->Result<implReply,Rejection>{
letdb=db.lock().await;
letorder=db.get_order(&order_id).await
.map_err(|e|warp::reject::custom(HandlerError::DbError(e)))?;
iforder.is_none(){returnErr(warp::reject::custom(HandlerError::OrderNotFound));}
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
db.process_payment(&order_id,&req.payment_method).await
.map_err(|e|warp::reject::custom(HandlerError::DbError(e)))?;
letpayment_successful=rand::thread_rng().gen_bool(0.8);
ifpayment_successful{
letorder=db.complete_payment(&order_id).await
.map_err(|e|warp::reject::custom(HandlerError::DbError(e)))?;
Ok(warp::reply::json(&json!({"status":"success",
"message":"Payment processed successfully","order":PaymentResponse::from(order)})))
}else{
db.update_payment_status(&order_id,PayStatus::Failed,Some(&req.payment_method),
).await.map_err(|e|warp::reject::custom(HandlerError::DbError(e)))?;
Err(warp::reject::custom(HandlerError::PaymentError))
}
}
pubasyncfnhandle_get_order(order_id:String,db:Arc<Mutex<DbClient>>,)->Result<implReply,Rejection>{
letdb=db.lock().await;
letorder=db.get_order(&order_id).await
.map_err(|e|warp::reject::custom(HandlerError::DbError(e)))?;
matchorder{Some(order)=>Ok(warp::reply::json(&PaymentResponse::from(order))),
None=>Err(warp::reject::custom(HandlerError::OrderNotFound)),}
}
pubasyncfnhandle_query_orders(query:QueryOrderRequest,
db:Arc<Mutex<DbClient>>,)->Result<implReply,Rejection>{
letdb=db.lock().await;
letorders=db.query_orders(query).await
.map_err(|e|warp::reject::custom(HandlerError::DbError(e)))?;
letresponses=orders.into_iter().map(PaymentResponse::from).collect::<Vec<_>>();
Ok(warp::reply::json(&responses))
}
pubfnpayment_routes(db:Arc<Mutex<DbClient>>,)->implFilter<Extract=implReply,Error=Rejection>+Clone{
letdb_filter=warp::any().map(move||Arc::clone(&db));
letpay_order=warp::path!("orders"/String/"pay")
.and(warp::post()).and(warp::body::json()).and(db_filter.clone())
.and_then(|order_id,req,db|asyncmove{handle_payment(order_id,req,db).await});
letget_order=warp::path!("orders"/String).and(warp::get()).and(db_filter.clone())
.and_then(|order_id,db|asyncmove{handle_get_order(order_id,db).await});
letquery_orders=warp::path!("orders").and(warp::get()).and(warp::query::<QueryOrderRequest>())
.and(db_filter.clone()).and_then(|query,db|asyncmove{handle_query_orders(query,db).await});
lethealth=warp::path!("health").and(warp::get())
.map(||warp::reply::json(&json!({"status":"ok"})));
pay_order.or(get_order).or(query_orders).or(health)
}
3.5 db.rs--数据库操作
usemongodb::{Client,Collection};usemongodb::bson::{doc,to_bson};
usemongodb::options::{ClientOptions,FindOptions};
usecrate::model::{Order,PayStatus,QueryOrderRequest,PaymentMethod};
usethiserror::Error;usechrono::Utc;userand::Rng;
#[derive(Error,Debug)]
pubenumDbError{
#[error("MongoDB error: {0}")]
MongoError(#[from]mongodb::error::Error),
#[error("BSON serialization error: {0}")]
BsonError(String),
#[error("Order not found")]
OrderNotFound,
}
implFrom<bson::ser::Error>forDbError{
fnfrom(error:bson::ser::Error)->Self{DbError::BsonError(error.to_string())}
}
pubstructDbClient{collection:Collection<Order>,}
implDbClient{
pubasyncfnnew(uri:&str,db_name:&str,collection_name:&str)->Result<Self,DbError>{
letclient_options=ClientOptions::parse(uri).await?;
letclient=Client::with_options(client_options)?;
letdb=client.database(db_name);letcollection=db.collection::<Order>(collection_name);
Ok(Self{collection})
}
pubasyncfnget_order(&self,order_id:&str)->Result<Option<Order>,DbError>{
letfilter=doc!{"order_id":order_id};
letorder=self.collection.find_one(filter,None).await?; Ok(order)
}
pubasyncfnupdate_payment_status(&self,order_id:&str,pay_status:PayStatus,
payment_method:Option<&PaymentMethod>,)->Result<(),DbError>{
letfilter=doc!{"order_id":order_id};
letupdate=doc!{
"$set":{"pay_status":to_bson(&pay_status)?,
"payment_method":payment_method.map(to_bson).transpose()?,
"updated_at":Utc::now().to_rfc3339()
}
};
letresult=self.collection.update_one(filter,update,None).await?;
ifresult.matched_count==0{returnErr(DbError::OrderNotFound);}
Ok(())
}
pubasyncfnprocess_payment(&self,order_id:&str,
payment_method:&PaymentMethod,)->Result<Order,DbError>{
letfilter=doc!{"order_id":order_id};
letupdate=doc!{
"$set":{"pay_status":to_bson(&PayStatus::Processing)?,
"payment_method":to_bson(payment_method)?,"updated_at":Utc::now().to_rfc3339()
}
};
letoptions=mongodb::options::FindOneAndUpdateOptions::builder()
.return_document(mongodb::options::ReturnDocument::After).build();
self.collection.find_one_and_update(filter,update,options).await?
.ok_or(DbError::OrderNotFound)
}
pubasyncfncomplete_payment(&self,order_id:&str)->Result<Order,DbError>{
lettransaction_id=format!("TRX{:08}",rand::thread_rng().gen_range(0..99999999));
letfilter=doc!{"order_id":order_id};
letupdate=doc!{
"$set":{"pay_status":to_bson(&PayStatus::Paid)?,
"transaction_id":transaction_id,"updated_at":Utc::now().to_rfc3339()
}
};
letoptions=mongodb::options::FindOneAndUpdateOptions::builder()
.return_document(mongodb::options::ReturnDocument::After).build();
self.collection.find_one_and_update(filter,update,options).await?
.ok_or(DbError::OrderNotFound)
}
pubasyncfnquery_orders(&self,query:QueryOrderRequest)->Result<Vec<Order>,DbError>{
letmutfilter=doc!{};
ifletSome(order_id)=query.order_id{filter.insert("order_id",order_id);}
ifletSome(user_id)=query.user_id{filter.insert("user_id",user_id);}
ifletSome(pay_status)=query.pay_status{filter.insert("pay_status",to_bson(&pay_status)?);}
letfind_options=FindOptions::builder().sort(doc!{"created_at":-1}).build();
letmutcursor=self.collection.find(filter,find_options).await?;
letmutorders=Vec::new();
whilecursor.advance().await?{
orders.push(cursor.deserialize_current()?);
}
Ok(orders)
}
}
3.6 Cargo.toml--依赖安装
[package]
name = "payment_service"
version= "0.1.0"
edition= "2024"
[dependencies]
warp = "0.3"
tokio = { version= "1.0", features= ["full"] }
serde = { version= "1.0", features= ["derive"] }
serde_json= "1.0"
mongodb= { version= "2.0", feature= ["sync"] }
bson = "2.0"
chrono= { version= "0.4", features= ["serde"] }
rand = "0.8"
thiserror= "1.0"
nacos-sdk= "0.4.0"
4 库存服务编码设计
4.1 功能设计
A. 库存模型设计:
应用 stock 表示总库存量;
reserved 表示已预留库存;
available 表示可用库存(stock - reserved);
version 字段用于乐观锁控制并发更新。
B. 与订单系统的兼容性:
使用相同的 product_id 字段与订单系统关联;
预留库存时可关联 order_id 便于追踪。
C. 错误处理:
自定义 DbError 并实现 Reject trait 用于统一错误处理;
区分库存不存在和库存不足的不同错误类型。
D. 并发控制:
运用 MongoDB 的原子执行确保库存更新的正确性;
乐观锁机制防止超卖。
E. API 设计:
RESTful 风格 API;
统一前缀 /api 便于网关路由;
承受 CORS 便于前端调用。
4.2 main.rs--服务入口与路由配置
usewarp::{Filter,Rejection,Reply};usestd::sync::Arc;usetokio::sync::Mutex;
usecrate::{db::DbClient,handler::inventory_routes};usestd::net::{IpAddr,Ipv4Addr,SocketAddr};
uselog::info;usestd::collections::HashMap;usenacos_sdk::api::props::ClientProps;
usenacos_sdk::api::naming::{NamingService,NamingServiceBuilder,ServiceInstance};
moddb;modhandler;modmodel;
#[tokio::main]
asyncfnmain()->Result<(),Box<dynstd::error::Error>>{
env_logger::init();info!("Starting inventory service");
// 服务配置
letservice_name="inventory_service".to_string();
letgroup="ecommerce".to_string();letip="127.0.0.1";letport=9003;
// 创建Nacos客户端
letclient=NamingServiceBuilder::new(
ClientProps::new().server_addr("127.0.0.1:8848").namespace("public"),).build()?;
// 构建服务实例(修正字段类型和命名)
letinstance=ServiceInstance{service_name:Some(service_name.clone()),
ip:ip.to_string(),port,weight:1.0,healthy:true,enabled:true,
ephemeral:true,instance_id:None,cluster_name:Some("DEFAULT".to_string()),
metadata:HashMap::from([("version".into(),"1.0".into()),("service".into(),"inventory".into())])};
// 注册服务(修正参数传递)
client.register_instance(service_name.clone(),Some(group.clone()),instance.clone()).await?;
println!("✅库存服务注册成功");
/* 服务注销逻辑(参数匹配)
client.deregister_instance( service_name, Some(group), instance ).await?; */
// 初始化MongoDB连接
letdb_client=Arc::new(Mutex::new(
DbClient::new("mongodb://localhost:27017","ecommerce1").await
.map_err(|e|{log::error!("Failed to initialize database client: {}",e);e})?));
// 定义 API 路由
letapi=warp::path("api");
letroutes=api.and(inventory_routes(db_client.clone()).with(warp::log("inventory_service")))
.with(warp::cors().allow_any_origin().allow_methods(vec!["GET","POST","PUT","DELETE"])
.allow_headers(vec!["Content-Type"])).recover(handle_rejection);
// 启动服务
letsocket_addr=SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)),9003);
info!("Starting inventory service on http://{}",socket_addr);
warp::serve(routes).run(socket_addr).await;Ok(())
}
asyncfnhandle_rejection(err:Rejection)->Result<implReply,std::convert::Infallible>{
let(code,message)=iferr.is_not_found(){
(warp::http::StatusCode::NOT_FOUND,"Not Found".to_string())
}elseifletSome(e)=err.find::<model::DbError>(){
matche{
model::DbError::InventoryNotFound=>
(warp::http::StatusCode::NOT_FOUND,"Inventory not found".to_string()),
model::DbError::InsufficientStock=>
(warp::http::StatusCode::BAD_REQUEST,"Insufficient stock".to_string()),
_=>
(warp::http::StatusCode::INTERNAL_SERVER_ERROR,"Database error".to_string()),
}
}else{
(warp::http::StatusCode::INTERNAL_SERVER_ERROR,"Internal Server Error".to_string())
};
Ok(warp::reply::with_status(message,code))
}
4.3 model.rs--数据模型定义
usebson::oid::ObjectId;useserde::{Deserialize,Serialize};usemongodb::bson::DateTime;
#[derive(Debug,Serialize,Deserialize)]
pubstructInventory{
#[serde(rename="_id",skip_serializing_if="Option::is_none")]
pubid:Option<ObjectId>,
pubproduct_id:String,
pubstock:i32,// 总库存量
pubreserved:i32,// 已预留库存
pubavailable:i32,// 可用库存
publast_updated:DateTime,
pubversion:i32,// 用于乐观锁控制
}
#[derive(Debug,Serialize,Deserialize)]
pubstructInventoryUpdateRequest{
pubproduct_id:String,
pubdelta:i32,// 正数表示增加库存,负数表示减少
}
#[derive(Debug,Serialize,Deserialize)]
pubstructInventoryQueryRequest{
pubproduct_id:String,
}
#[derive(Debug,Serialize,Deserialize)]
pubstructInventoryReserveRequest{
pubproduct_id:String,pubquantity:i32,
puborder_id:Option<String>,// 可选,关联订单ID
}
#[derive(Debug,Serialize,Deserialize)]
pubstructInventoryResponse{pubproduct_id:String,pubstock:i32,pubreserved:i32,
pubavailable:i32,publast_updated:String,}
#[derive(Debug,thiserror::Error)]
pubenumDbError{
#[error("Inventory not found")]
InventoryNotFound,
#[error("Insufficient stock")]
InsufficientStock,
#[error("Database error: {0}")]
MongoError(#[from]mongodb::error::Error),
#[error("Serialization error: {0}")]
BsonError(#[from]bson::ser::Error),
}
// 实现 warp 的Reject trait以便错误处理
implwarp::reject::RejectforDbError{}
4.4 handler.rs--请求处理逻辑
处理HTTP请求,调用数据库操作并返回响应。
usewarp::{Filter,Rejection,Reply};usestd::sync::Arc;usetokio::sync::Mutex;usecrate::db::DbClient;
usecrate::model::{Inventory,InventoryUpdateRequest,InventoryQueryRequest,InventoryReserveRequest,InventoryResponse};
pubfninventory_routes(db:Arc<Mutex<DbClient>>,)->implFilter<Extract=implReply,Error=Rejection>+Clone{
create_inventory(db.clone()).or(get_inventory(db.clone()))
.or(update_inventory(db.clone())).or(reserve_inventory(db.clone()))
}
fncreate_inventory(db:Arc<Mutex<DbClient>>,)->implFilter<Extract=implReply,Error=Rejection>+Clone{
warp::path!("inventory").and(warp::post()).and(with_db(db))
.and(warp::body::json()).and_then(create_inventory_handler)
}
fnget_inventory(db:Arc<Mutex<DbClient>>,)->implFilter<Extract=implReply,Error=Rejection>+Clone{
warp::path!("inventory").and(warp::get()).and(with_db(db))
.and(warp::query::<InventoryQueryRequest>()).and_then(get_inventory_handler)
}
fnupdate_inventory(db:Arc<Mutex<DbClient>>,)->implFilter<Extract=implReply,Error=Rejection>+Clone{
warp::path!("inventory"/"update").and(warp::post())
.and(with_db(db)).and(warp::body::json()).and_then(update_inventory_handler)
}
fnreserve_inventory(db:Arc<Mutex<DbClient>>,)->implFilter<Extract=implReply,Error=Rejection>+Clone{
warp::path!("inventory"/"reserve").and(warp::post()).and(with_db(db))
.and(warp::body::json()).and_then(reserve_inventory_handler)
}
fnwith_db(db:Arc<Mutex<DbClient>>)->implFilter<Extract=(Arc<Mutex<DbClient>>,),
Error=std::convert::Infallible>+Clone{
warp::any().map(move||db.clone())
}
asyncfncreate_inventory_handler(db:Arc<Mutex<DbClient>>,req:InventoryQueryRequest,
)->Result<implReply,Rejection>{
letdb=db.lock().await;
letinventory=db.create_inventory(&req.product_id,0).await.map_err(warp::reject::custom)?;
Ok(warp::reply::json(&to_response(inventory)))
}
asyncfnget_inventory_handler(db:Arc<Mutex<DbClient>>,req:InventoryQueryRequest,
)->Result<implReply,Rejection>{
letdb=db.lock().await;
letinventory=db.get_inventory(&req.product_id).await.map_err(warp::reject::custom)?;
Ok(warp::reply::json(&to_response(inventory)))
}
asyncfnupdate_inventory_handler(db:Arc<Mutex<DbClient>>,req:InventoryUpdateRequest,
)->Result<implReply,Rejection>{
letdb=db.lock().await;
letinventory=db.update_inventory(&req).await.map_err(warp::reject::custom)?;
Ok(warp::reply::json(&to_response(inventory)))
}
asyncfnreserve_inventory_handler(db:Arc<Mutex<DbClient>>,req:InventoryReserveRequest,
)->Result<implReply,Rejection>{
letdb=db.lock().await;
letinventory=db.reserve_inventory(&req).await.map_err(warp::reject::custom)?;
Ok(warp::reply::json(&to_response(inventory)))
}
fnto_response(inventory:Inventory)->InventoryResponse{
InventoryResponse{product_id:inventory.product_id,stock:inventory.stock,
reserved:inventory.reserved,available:inventory.available,
last_updated:inventory.last_updated.to_string(),
}
}
4.5 db.rs--数据库操作
usemongodb::{Client,Collection,options::ClientOptions};usemongodb::bson::doc;
usecrate::model::{Inventory,InventoryUpdateRequest,InventoryReserveRequest,DbError};
pubstructDbClient{inventory_collection:Collection<Inventory> }
implDbClient{
pubasyncfnnew(uri:&str,db_name:&str)->Result<Self,DbError>{
letclient_options=ClientOptions::parse(uri).await?;
letclient=Client::with_options(client_options)?;
letdb=client.database(db_name);
Ok(Self{inventory_collection:db.collection("inventory"),
//orders_collection: db.collection("orders"),
})
}
pubasyncfncreate_inventory(&self,product_id:&str,initial_stock:i32)->Result<Inventory,DbError>{
letnow=bson::DateTime::now();
letinventory=Inventory{id:None,product_id:product_id.to_string(),stock:initial_stock,
reserved:0,available:initial_stock,last_updated:now,version:1,};
letresult=self.inventory_collection.insert_one(inventory,None).await?;
letinserted_id=result.inserted_id.as_object_id().unwrap();
self.get_inventory_by_id(&inserted_id).await
}
pubasyncfnget_inventory(&self,product_id:&str)->Result<Inventory,DbError>{
self.inventory_collection.find_one(doc!{"product_id":product_id},None).await?
.ok_or(DbError::InventoryNotFound)
}
pubasyncfnupdate_inventory(&self,req:&InventoryUpdateRequest)->Result<Inventory,DbError>{
letfilter=doc!{"product_id":&req.product_id};
letupdate=doc!{"$inc":{"stock":req.delta,"available":req.delta,"version":1},
"$currentDate":{"last_updated":true}};
letoptions=mongodb::options::FindOneAndUpdateOptions::builder()
.return_document(mongodb::options::ReturnDocument::After).build();
self.inventory_collection.find_one_and_update(filter,update,options).await?
.ok_or(DbError::InventoryNotFound)
}
pubasyncfnreserve_inventory(&self,req:&InventoryReserveRequest)->Result<Inventory,DbError>{
letfilter=doc!{"product_id":&req.product_id,"available":{"$gte":req.quantity}};
letupdate=doc!{"$inc":{"reserved":req.quantity,"available":-req.quantity,"version":1},
"$currentDate":{"last_updated":true}
};
letoptions=mongodb::options::FindOneAndUpdateOptions::builder()
.return_document(mongodb::options::ReturnDocument::After).build();
self.inventory_collection.find_one_and_update(filter,update,options).await?
.ok_or(DbError::InsufficientStock)
}
asyncfnget_inventory_by_id(&self,id:&bson::oid::ObjectId)->Result<Inventory,DbError>{
self.inventory_collection.find_one(doc!{"_id":id},None).await?
.ok_or(DbError::InventoryNotFound)
}
}
4.6 Cargo.toml--依赖配备
[package]
name = "inventory_service"
version= "0.1.0"
edition= "2024"
[dependencies]
tokio = { version= "1.0", features= ["full"] }
warp = "0.3"
serde = { version= "1.0", features= ["derive"] }
serde_json= "1.0"
mongodb= { version= "2.4", feature= ["sync"] }
bson = "2.4"
futures= "0.3"
log = "0.4"
env_logger= "0.9"
thiserror= "1.0"
nacos-sdk= "0.4.0"