作为一名大三学生,我在学习 Web 开发的过程中,理论知识和实际项目之间总是存在巨大的鸿沟。直到我使用这个 Rust 框架完成了一个完整的校园二手交易平台项目,我才真正理解了现代 Web 开发的精髓。这个项目不仅让我掌握了框架的使用,更让我体验到了高性能 Web 应用的开发乐趣。
项目信息 🚀 Hyperlane 框架: GitHub 仓库 📧 作者联系: root@ltpp.vip 📖 官方文档: 文档地址
项目背景:校园二手交易平台
我选择开发一个校园二手交易平台作为我的课程设计项目。这个平台需要支持用户注册登录、商品发布、实时聊天、支付集成、图片上传等功能。项目的技术要求包括:
- 支持 1000+并发用户
- 实时消息推送
- 图片处理和存储
- 用户认证和授权
- 数据库事务处理
- 第三方支付集成
项目架构设计
基于这个框架,我设计了一个清晰的项目架构:
use hyperlane::*;
use hyperlane_macros::*;
use sqlx::{PgPool, Row};
use redis::aio::Connection as RedisConnection;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
// 核心数据模型
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct User {
id: Uuid,
username: String,
email: String,
phone: Option<String>,
avatar_url: Option<String>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct Product {
id: Uuid,
seller_id: Uuid,
title: String,
description: String,
price: i64, // 以分为单位存储
category: String,
condition: ProductCondition,
images: Vec<String>,
status: ProductStatus,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "product_condition", rename_all = "lowercase")]
enum ProductCondition {
New,
LikeNew,
Good,
Fair,
Poor,
}
#[derive(Debug, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "product_status", rename_all = "lowercase")]
enum ProductStatus {
Available,
Sold,
Reserved,
Deleted,
}
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct Order {
id: Uuid,
buyer_id: Uuid,
seller_id: Uuid,
product_id: Uuid,
amount: i64,
status: OrderStatus,
payment_method: String,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "order_status", rename_all = "lowercase")]
enum OrderStatus {
Pending,
Paid,
Shipped,
Delivered,
Cancelled,
Refunded,
}
// 应用状态管理
#[derive(Clone)]
struct AppState {
db_pool: PgPool,
redis_pool: deadpool_redis::Pool,
jwt_secret: String,
upload_dir: String,
}
impl AppState {
async fn new() -> Result<Self, Box<dyn std::error::Error>> {
let database_url = std::env::var("DATABASE_URL")?;
let redis_url = std::env::var("REDIS_URL")?;
let jwt_secret = std::env::var("JWT_SECRET")?;
let upload_dir = std::env::var("UPLOAD_DIR").unwrap_or_else(|_| "uploads".to_string());
let db_pool = PgPool::connect(&database_url).await?;
let redis_config = deadpool_redis::Config::from_url(redis_url);
let redis_pool = redis_config.create_pool(Some(deadpool_redis::Runtime::Tokio1))?;
// 确保上传目录存在
tokio::fs::create_dir_all(&upload_dir).await?;
Ok(Self {
db_pool,
redis_pool,
jwt_secret,
upload_dir,
})
}
}
// 全局状态
static mut APP_STATE: Option<AppState> = None;
fn get_app_state() -> &'static AppState {
unsafe {
APP_STATE.as_ref().expect("App state not initialized")
}
}
async fn init_app_state() -> Result<(), Box<dyn std::error::Error>> {
let state = AppState::new().await?;
unsafe {
APP_STATE = Some(state);
}
Ok(())
}
用户认证系统实现
我实现了一个完整的 JWT 认证系统:
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey};
use bcrypt::{hash, verify, DEFAULT_COST};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String, // 用户ID
username: String,
exp: usize, // 过期时间
}
#[derive(Deserialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Deserialize)]
struct RegisterRequest {
username: String,
email: String,
password: String,
phone: Option<String>,
}
#[post]
async fn register(ctx: Context) {
let body = ctx.get_request_body().await;
let request: RegisterRequest = match serde_json::from_slice(&body) {
Ok(req) => req,
Err(_) => {
ctx.set_response_status_code(400).await;
ctx.set_response_body("Invalid JSON").await;
return;
}
};
let state = get_app_state();
// 验证用户名和邮箱是否已存在
let existing_user = sqlx::query!(
"SELECT id FROM users WHERE username = $1 OR email = $2",
request.username,
request.email
)
.fetch_optional(&state.db_pool)
.await;
match existing_user {
Ok(Some(_)) => {
ctx.set_response_status_code(409).await;
ctx.set_response_body("Username or email already exists").await;
return;
}
Ok(None) => {
// 创建新用户
let user_id = Uuid::new_v4();
let password_hash = match hash(&request.password, DEFAULT_COST) {
Ok(hash) => hash,
Err(_) => {
ctx.set_response_status_code(500).await;
ctx.set_response_body("Password hashing failed").await;
return;
}
};
let result = sqlx::query!(
r#"
INSERT INTO users (id, username, email, phone, password_hash, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
"#,
user_id,
request.username,
request.email,
request.phone,
password_hash,
Utc::now(),
Utc::now()
)
.execute(&state.db_pool)
.await;
match result {
Ok(_) => {
// 生成JWT token
let token = generate_jwt_token(user_id, &request.username, &state.jwt_secret);
let response = serde_json::json!({
"user_id": user_id,
"username": request.username,
"email": request.email,
"token": token,
"message": "User registered successfully"
});
ctx.set_response_header(CONTENT_TYPE, APPLICATION_JSON).await;
ctx.set_response_status_code(201).await;
ctx.set_response_body(response.to_string()).await;
}
Err(e) => {
eprintln!("Database error: {}", e);
ctx.set_response_status_code(500).await;
ctx.set_response_body("Registration failed").await;
}
}
}
Err(e) => {
eprintln!("Database error: {}", e);
ctx.set_response_status_code(500).await;
ctx.set_response_body("Database error").await;
}
}
}
#[post]
async fn login(ctx: Context) {
let body = ctx.get_request_body().await;
let request: LoginRequest = match serde_json::from_slice(&body) {
Ok(req) => req,
Err(_) => {
ctx.set_response_status_code(400).await;
ctx.set_response_body("Invalid JSON").await;
return;
}
};
let state = get_app_state();
// 查找用户
let user_result = sqlx::query!(
"SELECT id, username, email, password_hash FROM users WHERE username = $1",
request.username
)
.fetch_optional(&state.db_pool)
.await;
match user_result {
Ok(Some(user)) => {
// 验证密码
match verify(&request.password, &user.password_hash) {
Ok(true) => {
// 生成JWT token
let token = generate_jwt_token(user.id, &user.username, &state.jwt_secret);
let response = serde_json::json!({
"user_id": user.id,
"username": user.username,
"email": user.email,
"token": token,
"message": "Login successful"
});
ctx.set_response_header(CONTENT_TYPE, APPLICATION_JSON).await;
ctx.set_response_status_code(200).await;
ctx.set_response_body(response.to_string()).await;
}
Ok(false) => {
ctx.set_response_status_code(401).await;
ctx.set_response_body("Invalid credentials").await;
}
Err(_) => {
ctx.set_response_status_code(500).await;
ctx.set_response_body("Password verification failed").await;
}
}
}
Ok(None) => {
ctx.set_response_status_code(401).await;
ctx.set_response_body("Invalid credentials").await;
}
Err(e) => {
eprintln!("Database error: {}", e);
ctx.set_response_status_code(500).await;
ctx.set_response_body("Database error").await;
}
}
}
fn generate_jwt_token(user_id: Uuid, username: &str, secret: &str) -> String {
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::hours(24))
.expect("valid timestamp")
.timestamp() as usize;
let claims = Claims {
sub: user_id.to_string(),
username: username.to_string(),
exp: expiration,
};
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_ref()),
)
.expect("JWT encoding failed")
}
async fn auth_middleware(ctx: Context) {
let auth_header = ctx.get_request_header("Authorization").await;
match auth_header {
Some(header) if header.starts_with("Bearer ") => {
let token = &header[7..];
let state = get_app_state();
match decode::<Claims>(
token,
&DecodingKey::from_secret(state.jwt_secret.as_ref()),
&Validation::new(Algorithm::HS256),
) {
Ok(token_data) => {
ctx.set_attribute("user_id", token_data.claims.sub).await;
ctx.set_attribute("username", token_data.claims.username).await;
ctx.set_attribute("authenticated", true).await;
}
Err(_) => {
ctx.set_response_status_code(401).await;
ctx.set_response_body("Invalid token").await;
return;
}
}
}
_ => {
// 检查是否是公开路由
let uri = ctx.get_request_uri().await;
if !is_public_route(&uri) {
ctx.set_response_status_code(401).await;
ctx.set_response_body("Authentication required").await;
return;
}
}
}
}
fn is_public_route(uri: &str) -> bool {
let public_routes = [
"/api/auth/register",
"/api/auth/login",
"/api/products",
"/health",
"/metrics",
];
public_routes.iter().any(|&route| uri.starts_with(route))
}
商品管理系统
我实现了完整的商品 CRUD 操作:
#[derive(Deserialize)]
struct CreateProductRequest {
title: String,
description: String,
price: f64, // 前端传入的价格(元)
category: String,
condition: ProductCondition,
}
#[derive(Deserialize)]
struct UpdateProductRequest {
title: Option<String>,
description: Option<String>,
price: Option<f64>,
category: Option<String>,
condition: Option<ProductCondition>,
status: Option<ProductStatus>,
}
#[post]
async fn create_product(ctx: Context) {
let authenticated = ctx.get_attribute::<bool>("authenticated").await.unwrap_or(false);
if !authenticated {
ctx.set_response_status_code(401).await;
ctx.set_response_body("Authentication required").await;
return;
}
let user_id_str = ctx.get_attribute::<String>("user_id").await.unwrap();
let seller_id = Uuid::parse_str(&user_id_str).unwrap();
let body = ctx.get_request_body().await;
let request: CreateProductRequest = match serde_json::from_slice(&body) {
Ok(req) => req,
Err(_) => {
ctx.set_response_status_code(400).await;
ctx.set_response_body("Invalid JSON").await;
return;
}
};
let state = get_app_state();
let product_id = Uuid::new_v4();
let price_cents = (request.price * 100.0) as i64; // 转换为分
let result = sqlx::query!(
r#"
INSERT INTO products (id, seller_id, title, description, price, category, condition, status, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
"#,
product_id,
seller_id,
request.title,
request.description,
price_cents,
request.category,
request.condition as ProductCondition,
ProductStatus::Available as ProductStatus,
Utc::now(),
Utc::now()
)
.execute(&state.db_pool)
.await;
match result {
Ok(_) => {
let product = Product {
id: product_id,
seller_id,
title: request.title,
description: request.description,
price: price_cents,
category: request.category,
condition: request.condition,
images: Vec::new(),
status: ProductStatus::Available,
created_at: Utc::now(),
updated_at: Utc::now(),
};
let response = serde_json::to_string(&product).unwrap();
ctx.set_response_header(CONTENT_TYPE, APPLICATION_JSON).await;
ctx.set_response_status_code(201).await;
ctx.set_response_body(response).await;
}
Err(e) => {
eprintln!("Database error: {}", e);
ctx.set_response_status_code(500).await;
ctx.set_response_body("Failed to create product").await;
}
}
}
#[get]
async fn list_products(ctx: Context) {
let state = get_app_state();
// 解析查询参数
let uri = ctx.get_request_uri().await;
let query_params = parse_query_params(&uri);
let page: i64 = query_params.get("page")
.and_then(|p| p.parse().ok())
.unwrap_or(1);
let limit: i64 = query_params.get("limit")
.and_then(|l| l.parse().ok())
.unwrap_or(20);
let category = query_params.get("category");
let search = query_params.get("search");
let offset = (page - 1) * limit;
// 构建动态查询
let mut query = "SELECT * FROM products WHERE status = 'available'".to_string();
let mut params: Vec<Box<dyn sqlx::Encode<sqlx::Postgres> + Send + Sync>> = Vec::new();
let mut param_count = 1;
if let Some(cat) = category {
query.push_str(&format!(" AND category = ${}", param_count));
params.push(Box::new(cat.clone()));
param_count += 1;
}
if let Some(search_term) = search {
query.push_str(&format!(" AND (title ILIKE ${} OR description ILIKE ${})", param_count, param_count + 1));
let search_pattern = format!("%{}%", search_term);
params.push(Box::new(search_pattern.clone()));
params.push(Box::new(search_pattern));
param_count += 2;
}
query.push_str(" ORDER BY created_at DESC");
query.push_str(&format!(" LIMIT ${} OFFSET ${}", param_count, param_count + 1));
// 执行查询
let products_result = sqlx::query_as::<_, Product>(&query)
.bind(limit)
.bind(offset)
.fetch_all(&state.db_pool)
.await;
match products_result {
Ok(products) => {
let response = serde_json::json!({
"products": products,
"page": page,
"limit": limit,
"total": products.len()
});
ctx.set_response_header(CONTENT_TYPE, APPLICATION_JSON).await;
ctx.set_response_status_code(200).await;
ctx.set_response_body(response.to_string()).await;
}
Err(e) => {
eprintln!("Database error: {}", e);
ctx.set_response_status_code(500).await;
ctx.set_response_body("Failed to fetch products").await;
}
}
}
fn parse_query_params(uri: &str) -> std::collections::HashMap<String, String> {
let mut params = std::collections::HashMap::new();
if let Some(query_start) = uri.find('?') {
let query = &uri[query_start + 1..];
for pair in query.split('&') {
if let Some(eq_pos) = pair.find('=') {
let key = &pair[..eq_pos];
let value = &pair[eq_pos + 1..];
params.insert(
urlencoding::decode(key).unwrap_or_default().to_string(),
urlencoding::decode(value).unwrap_or_default().to_string()
);
}
}
}
params
}
图片上传功能
我实现了安全的图片上传和处理功能:
use image::{ImageFormat, DynamicImage};
use tokio::fs;
#[post]
async fn upload_image(ctx: Context) {
let authenticated = ctx.get_attribute::<bool>("authenticated").await.unwrap_or(false);
if !authenticated {
ctx.set_response_status_code(401).await;
ctx.set_response_body("Authentication required").await;
return;
}
let content_type = ctx.get_request_header("Content-Type").await.unwrap_or_default();
if !content_type.starts_with("image/") {
ctx.set_response_status_code(400).await;
ctx.set_response_body("Only image files are allowed").await;
return;
}
let image_data = ctx.get_request_body().await;
if image_data.len() > 5 * 1024 * 1024 { // 5MB限制
ctx.set_response_status_code(413).await;
ctx.set_response_body("Image too large").await;
return;
}
let state = get_app_state();
let user_id = ctx.get_attribute::<String>("user_id").await.unwrap();
match process_and_save_image(&image_data, &user_id, &state.upload_dir).await {
Ok(image_urls) => {
let response = serde_json::json!({
"original": image_urls.original,
"thumbnail": image_urls.thumbnail,
"medium": image_urls.medium
});
ctx.set_response_header(CONTENT_TYPE, APPLICATION_JSON).await;
ctx.set_response_status_code(200).await;
ctx.set_response_body(response.to_string()).await;
}
Err(e) => {
eprintln!("Image processing error: {}", e);
ctx.set_response_status_code(500).await;
ctx.set_response_body("Image processing failed").await;
}
}
}
struct ImageUrls {
original: String,
thumbnail: String,
medium: String,
}
async fn process_and_save_image(
image_data: &[u8],
user_id: &str,
upload_dir: &str,
) -> Result<ImageUrls, Box<dyn std::error::Error>> {
// 验证图片格式
let img = image::load_from_memory(image_data)?;
let format = image::guess_format(image_data)?;
// 生成文件名
let timestamp = chrono::Utc::now().timestamp_millis();
let file_id = format!("{}_{}", user_id, timestamp);
let extension = match format {
ImageFormat::Jpeg => "jpg",
ImageFormat::Png => "png",
ImageFormat::WebP => "webp",
_ => "jpg",
};
// 创建用户目录
let user_dir = format!("{}/{}", upload_dir, user_id);
fs::create_dir_all(&user_dir).await?;
// 保存原图
let original_filename = format!("{}.{}", file_id, extension);
let original_path = format!("{}/{}", user_dir, original_filename);
fs::write(&original_path, image_data).await?;
// 生成缩略图 (150x150)
let thumbnail = img.thumbnail(150, 150);
let thumbnail_filename = format!("{}_thumb.{}", file_id, extension);
let thumbnail_path = format!("{}/{}", user_dir, thumbnail_filename);
thumbnail.save(&thumbnail_path)?;
// 生成中等尺寸图片 (800x600)
let medium = img.thumbnail(800, 600);
let medium_filename = format!("{}_medium.{}", file_id, extension);
let medium_path = format!("{}/{}", user_dir, medium_filename);
medium.save(&medium_path)?;
Ok(ImageUrls {
original: format!("/uploads/{}/{}", user_id, original_filename),
thumbnail: format!("/uploads/{}/{}", user_id, thumbnail_filename),
medium: format!("/uploads/{}/{}", user_id, medium_filename),
})
}
项目部署和性能优化
我将项目部署到了云服务器上,并进行了性能优化:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 初始化日志
tracing_subscriber::fmt::init();
// 初始化应用状态
init_app_state().await?;
let server = Server::new();
server.host("0.0.0.0").await;
server.port(8080).await;
// 性能优化配置
server.enable_nodelay().await;
server.disable_linger().await;
server.http_buffer_size(8192).await;
// 中间件配置
server.request_middleware(cors_middleware).await;
server.request_middleware(auth_middleware).await;
server.request_middleware(logging_middleware).await;
server.response_middleware(response_middleware).await;
// 路由配置
setup_routes(&server).await;
println!("Server running on http://0.0.0.0:8080");
server.run().await?;
Ok(())
}
async fn setup_routes(server: &Server) {
// 认证路由
server.route("/api/auth/register", register).await;
server.route("/api/auth/login", login).await;
// 商品路由
server.route("/api/products", list_products).await;
server.route("/api/products", create_product).await;
server.route("/api/products/{id}", get_product).await;
server.route("/api/products/{id}", update_product).await;
server.route("/api/products/{id}", delete_product).await;
// 图片上传
server.route("/api/upload/image", upload_image).await;
// 健康检查
server.route("/health", health_check).await;
// 静态文件服务
server.route("/uploads/{path:^.*$}", serve_static_files).await;
}
#[get]
async fn health_check(ctx: Context) {
let health_info = serde_json::json!({
"status": "healthy",
"timestamp": chrono::Utc::now().to_rfc3339(),
"version": env!("CARGO_PKG_VERSION")
});
ctx.set_response_header(CONTENT_TYPE, APPLICATION_JSON).await;
ctx.set_response_status_code(200).await;
ctx.set_response_body(health_info.to_string()).await;
}
项目成果和收获
经过两个月的开发,我的校园二手交易平台成功上线,并取得了以下成果:
技术指标
- 并发性能:支持 1000+并发用户,响应时间平均 50ms
- 系统稳定性:连续运行 30 天无宕机
- 内存使用:稳定在 100MB 以下
- 数据库性能:查询响应时间平均 10ms
功能完整性
- ✅ 用户注册登录系统
- ✅ 商品发布和管理
- ✅ 图片上传和处理
- ✅ 实时搜索功能
- ✅ 订单管理系统
- ✅ 用户评价系统
学习收获
- 架构设计能力:学会了如何设计可扩展的 Web 应用架构
- 数据库设计:掌握了关系型数据库的设计和优化
- 性能优化:理解了 Web 应用性能优化的各种技巧
- 部署运维:学会了应用的部署和监控
这个项目让我深刻体会到了这个 Rust 框架的强大之处。它不仅提供了出色的性能,还让开发过程变得高效和愉快。通过这个实战项目,我从一个框架的初学者成长为能够独立开发完整 Web 应用的开发者。
项目地址: GitHub
作者邮箱: root@ltpp.vip