分布微服务电商订单系统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 架构原则

  1. 分层设计:业务逻辑(handler)、数据访问(db)、模型(model)分离。
  2. 异步非阻塞:基于Tokio运行时,高并发下性能优异。
  3. 安全序列化:Serde确保JSON与结构体转换的类型安全。

2.2 订单微服务实现功能

  1. 订单创建:依据POST /orders接口创建新订单
  2. 订单查询:通过GET /orders/{order_id}接口查询订单详情
  3. 订单确认:通过PUT /orders/{order_id}/confirm接口确认订单
  4. 订单刷新:利用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"

posted @ 2025-08-12 21:00  yfceshi  阅读(16)  评论(0)    收藏  举报