rust编写web服务02-路由与请求处理
第2堂:路由与请求处理
🎯 学习目标
通过本堂课,您将学会:
- 设计RESTful API路由结构
- 处理不同类型的请求参数(路径、查询、请求体)
- 使用Serde进行数据序列化和反序列化
- 实现完整的CRUD操作路由
- 理解Axum的路由匹配和参数提取机制
📋 课程大纲
- RESTful API设计原则
- 路由定义与组织
- 请求参数处理
- JSON数据序列化
- CRUD操作实现
- 路由分组与中间件
🏗️ RESTful API设计原则
REST架构风格
REST(Representational State Transfer)是一种软件架构风格,具有以下特点:
- 资源导向: 所有内容都是资源,用URI标识
- 统一接口: 使用标准HTTP方法
- 无状态: 每个请求包含所有必要信息
- 可缓存: 响应可以被缓存
- 分层系统: 客户端无需知道是否直接连接到服务器
HTTP方法映射
HTTP方法 | 用途 | 示例 |
---|---|---|
GET | 获取资源 | GET /api/v1/users |
POST | 创建资源 | POST /api/v1/users |
PUT | 更新整个资源 | PUT /api/v1/users/1 |
PATCH | 部分更新资源 | PATCH /api/v1/users/1 |
DELETE | 删除资源 | DELETE /api/v1/users/1 |
API版本控制
// 推荐使用路径版本控制
/api/v1/users
/api/v2/users// 或使用头部版本控制
Accept: application/vnd.api.v1+json
🛣️ 路由定义与组织
基本路由结构
// src/main.rs
use axum::{routing::{get, post, put, delete, patch},Router,extract::{Path, Query, Json},response::Json as ResponseJson,
};#[tokio::main]
async fn main() {let app = Router::new()// 用户相关路由.route("/api/v1/users", get(get_users).post(create_user)).route("/api/v1/users/:id", get(get_user).put(update_user).delete(delete_user)).route("/api/v1/users/:id", patch(partial_update_user))// 文章相关路由.route("/api/v1/posts", get(get_posts).post(create_post)).route("/api/v1/posts/:id", get(get_post).put(update_post).delete(delete_post))// 分类相关路由.route("/api/v1/categories", get(get_categories).post(create_category));let addr = SocketAddr::from(([127, 0, 0, 1], 3000));let listener = tokio::net::TcpListener::bind(addr).await.unwrap();axum::serve(listener, app).await.unwrap();
}
路由模块化
// src/handlers/mod.rs
use axum::{routing::{get, post, put, delete, patch},Router,
};pub mod users;
pub mod posts;
pub mod categories;pub fn create_router() -> Router {Router::new().nest("/api/v1/users", users::routes()).nest("/api/v1/posts", posts::routes()).nest("/api/v1/categories", categories::routes())
}// src/handlers/users.rs
use axum::{routing::{get, post, put, delete, patch},Router,extract::{Path, Query, Json},response::Json as ResponseJson,
};use crate::models::user::{User, CreateUser, UpdateUser, UserQuery};pub fn routes() -> Router {Router::new().route("/", get(get_users).post(create_user)).route("/:id", get(get_user).put(update_user).delete(delete_user)).route("/:id", patch(partial_update_user))
}// 处理器函数
pub async fn get_users(Query(params): Query<UserQuery>) -> ResponseJson<Vec<User>> {// 实现获取用户列表逻辑todo!()
}pub async fn create_user(Json(payload): Json<CreateUser>) -> ResponseJson<User> {// 实现创建用户逻辑todo!()
}pub async fn get_user(Path(id): Path<u32>) -> ResponseJson<User> {// 实现获取单个用户逻辑todo!()
}pub async fn update_user(Path(id): Path<u32>, Json(payload): Json<UpdateUser>) -> ResponseJson<User> {// 实现更新用户逻辑todo!()
}pub async fn delete_user(Path(id): Path<u32>) -> ResponseJson<()> {// 实现删除用户逻辑todo!()
}pub async fn partial_update_user(Path(id): Path<u32>, Json(payload): Json<UpdateUser>) -> ResponseJson<User> {// 实现部分更新用户逻辑todo!()
}
📥 请求参数处理
路径参数提取
use axum::extract::Path;// 单个路径参数
pub async fn get_user(Path(id): Path<u32>) -> ResponseJson<User> {println!("获取用户ID: {}", id);// 实现逻辑
}// 多个路径参数
pub async fn get_user_post(Path((user_id, post_id)): Path<(u32, u32)>) -> ResponseJson<Post> {println!("获取用户 {} 的文章 {}", user_id, post_id);// 实现逻辑
}// 使用结构体提取路径参数
#[derive(serde::Deserialize)]
struct UserPostPath {user_id: u32,post_id: u32,
}pub async fn get_user_post_struct(Path(params): Path<UserPostPath>) -> ResponseJson<Post> {println!("获取用户 {} 的文章 {}", params.user_id, params.post_id);// 实现逻辑
}
查询参数处理
use axum::extract::Query;
use serde::Deserialize;// 基本查询参数
#[derive(Deserialize)]
pub struct UserQuery {pub page: Option<u32>,pub limit: Option<u32>,pub search: Option<String>,pub sort_by: Option<String>,pub order: Option<String>, // "asc" or "desc"
}pub async fn get_users(Query(params): Query<UserQuery>) -> ResponseJson<Vec<User>> {let page = params.page.unwrap_or(1);let limit = params.limit.unwrap_or(10);let search = params.search.as_deref().unwrap_or("");let sort_by = params.sort_by.as_deref().unwrap_or("id");let order = params.order.as_deref().unwrap_or("asc");println!("查询参数: page={}, limit={}, search='{}', sort_by='{}', order='{}'", page, limit, search, sort_by, order);// 实现查询逻辑todo!()
}// 复杂查询参数
#[derive(Deserialize)]
pub struct AdvancedUserQuery {pub page: Option<u32>,pub limit: Option<u32>,pub filters: Option<UserFilters>,
}#[derive(Deserialize)]
pub struct UserFilters {pub name: Option<String>,pub email: Option<String>,pub status: Option<UserStatus>,pub created_after: Option<chrono::DateTime<chrono::Utc>>,pub created_before: Option<chrono::DateTime<chrono::Utc>>,
}#[derive(Deserialize)]
pub enum UserStatus {Active,Inactive,Pending,
}
请求体处理
use axum::extract::Json;
use serde::{Deserialize, Serialize};// 创建用户请求体
#[derive(Deserialize)]
pub struct CreateUser {pub username: String,pub email: String,pub password: String,pub full_name: Option<String>,
}// 更新用户请求体
#[derive(Deserialize)]
pub struct UpdateUser {pub username: Option<String>,pub email: Option<String>,pub full_name: Option<String>,
}// 批量操作请求体
#[derive(Deserialize)]
pub struct BatchCreateUsers {pub users: Vec<CreateUser>,
}pub async fn create_user(Json(payload): Json<CreateUser>) -> ResponseJson<User> {// 验证输入数据if payload.username.is_empty() || payload.email.is_empty() {return ResponseJson(User::default()); // 实际应该返回错误}println!("创建用户: {:?}", payload);// 实现创建逻辑todo!()
}pub async fn batch_create_users(Json(payload): Json<BatchCreateUsers>) -> ResponseJson<Vec<User>> {println!("批量创建用户数量: {}", payload.users.len());// 实现批量创建逻辑todo!()
}
📤 JSON数据序列化
响应模型定义
// src/models/user.rs
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct User {pub id: u32,pub username: String,pub email: String,pub full_name: Option<String>,pub status: UserStatus,pub created_at: DateTime<Utc>,pub updated_at: DateTime<Utc>,
}#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum UserStatus {Active,Inactive,Pending,
}// 响应包装器
#[derive(Debug, Serialize)]
pub struct ApiResponse<T> {pub success: bool,pub data: Option<T>,pub message: Option<String>,pub timestamp: DateTime<Utc>,
}impl<T> ApiResponse<T> {pub fn success(data: T) -> Self {Self {success: true,data: Some(data),message: None,timestamp: Utc::now(),}}pub fn error(message: String) -> Self {Self {success: false,data: None,message: Some(message),timestamp: Utc::now(),}}
}// 分页响应
#[derive(Debug, Serialize)]
pub struct PaginatedResponse<T> {pub data: Vec<T>,pub pagination: Pagination,
}#[derive(Debug, Serialize)]
pub struct Pagination {pub page: u32,pub limit: u32,pub total: u64,pub total_pages: u32,pub has_next: bool,pub has_prev: bool,
}
统一响应格式
// src/handlers/users.rs
use axum::response::Json;pub async fn get_users(Query(params): Query<UserQuery>) -> Json<ApiResponse<PaginatedResponse<User>>> {let users = vec![User {id: 1,username: "john_doe".to_string(),email: "john@example.com".to_string(),full_name: Some("John Doe".to_string()),status: UserStatus::Active,created_at: Utc::now(),updated_at: Utc::now(),},// 更多用户数据...];let pagination = Pagination {page: params.page.unwrap_or(1),limit: params.limit.unwrap_or(10),total: users.len() as u64,total_pages: 1,has_next: false,has_prev: false,};let response = PaginatedResponse {data: users,pagination,};Json(ApiResponse::success(response))
}
🔄 CRUD操作实现
完整的用户CRUD示例
// src/handlers/users.rs
use uuid::Uuid;// 获取用户列表
pub async fn get_users(Query(params): Query<UserQuery>) -> Json<ApiResponse<PaginatedResponse<User>>> {// 模拟数据库查询let users = simulate_database_query(¶ms).await;let pagination = Pagination {page: params.page.unwrap_or(1),limit: params.limit.unwrap_or(10),total: users.len() as u64,total_pages: (users.len() as f64 / params.limit.unwrap_or(10) as f64).ceil() as u32,has_next: false,has_prev: false,};let response = PaginatedResponse { data: users, pagination };Json(ApiResponse::success(response))
}// 获取单个用户
pub async fn get_user(Path(id): Path<u32>) -> Json<ApiResponse<User>> {match simulate_get_user_by_id(id).await {Some(user) => Json(ApiResponse::success(user)),None => Json(ApiResponse::error("用户不存在".to_string())),}
}// 创建用户
pub async fn create_user(Json(payload): Json<CreateUser>) -> Json<ApiResponse<User>> {// 验证输入if payload.username.is_empty() || payload.email.is_empty() {return Json(ApiResponse::error("用户名和邮箱不能为空".to_string()));}// 检查用户是否已存在if simulate_user_exists(&payload.email).await {return Json(ApiResponse::error("邮箱已被使用".to_string()));}let new_user = User {id: generate_user_id(),username: payload.username,email: payload.email,full_name: payload.full_name,status: UserStatus::Active,created_at: Utc::now(),updated_at: Utc::now(),};// 保存到数据库simulate_save_user(&new_user).await;Json(ApiResponse::success(new_user))
}// 更新用户
pub async fn update_user(Path(id): Path<u32>,Json(payload): Json<UpdateUser>
) -> Json<ApiResponse<User>> {let mut user = match simulate_get_user_by_id(id).await {Some(user) => user,None => return Json(ApiResponse::error("用户不存在".to_string())),};// 更新字段if let Some(username) = payload.username {user.username = username;}if let Some(email) = payload.email {user.email = email;}if let Some(full_name) = payload.full_name {user.full_name = Some(full_name);}user.updated_at = Utc::now();// 保存更新simulate_save_user(&user).await;Json(ApiResponse::success(user))
}// 删除用户
pub async fn delete_user(Path(id): Path<u32>) -> Json<ApiResponse<()>> {match simulate_delete_user(id).await {true => Json(ApiResponse::success(())),false => Json(ApiResponse::error("用户不存在或删除失败".to_string())),}
}// 模拟数据库操作的辅助函数
async fn simulate_database_query(params: &UserQuery) -> Vec<User> {// 模拟数据库查询延迟tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;vec![User {id: 1,username: "john_doe".to_string(),email: "john@example.com".to_string(),full_name: Some("John Doe".to_string()),status: UserStatus::Active,created_at: Utc::now(),updated_at: Utc::now(),},// 更多模拟数据...]
}async fn simulate_get_user_by_id(id: u32) -> Option<User> {if id == 1 {Some(User {id: 1,username: "john_doe".to_string(),email: "john@example.com".to_string(),full_name: Some("John Doe".to_string()),status: UserStatus::Active,created_at: Utc::now(),updated_at: Utc::now(),})} else {None}
}async fn simulate_user_exists(email: &str) -> bool {email == "existing@example.com"
}async fn simulate_save_user(_user: &User) {// 模拟保存延迟tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}async fn simulate_delete_user(id: u32) -> bool {id > 0
}fn generate_user_id() -> u32 {// 简单的ID生成器chrono::Utc::now().timestamp() as u32
}
🔧 路由分组与中间件
路由分组
// src/main.rs
use axum::{Router,middleware,
};mod handlers;#[tokio::main]
async fn main() {let app = Router::new()// 公开路由(无需认证).route("/", get(root)).route("/health", get(health_check)).route("/api/v1/auth/login", post(handlers::auth::login)).route("/api/v1/auth/register", post(handlers::auth::register))// 需要认证的路由组.nest("/api/v1", protected_routes())// 管理员路由组.nest("/api/v1/admin", admin_routes());// 启动服务器let addr = SocketAddr::from(([127, 0, 0, 1], 3000));let listener = tokio::net::TcpListener::bind(addr).await.unwrap();axum::serve(listener, app).await.unwrap();
}fn protected_routes() -> Router {Router::new().nest("/users", handlers::users::routes()).nest("/posts", handlers::posts::routes()).nest("/categories", handlers::categories::routes())// 添加认证中间件.layer(middleware::from_fn(auth_middleware))
}fn admin_routes() -> Router {Router::new().route("/users", get(handlers::admin::get_all_users)).route("/stats", get(handlers::admin::get_stats))// 添加管理员权限中间件.layer(middleware::from_fn(admin_middleware))
}// 认证中间件
async fn auth_middleware(mut req: axum::extract::Request,next: axum::middleware::Next,
) -> Result<axum::response::Response, axum::http::StatusCode> {// 实现JWT验证逻辑// 这里暂时跳过,在第6堂课中详细讲解next.run(req).await;Ok(())
}// 管理员权限中间件
async fn admin_middleware(mut req: axum::extract::Request,next: axum::middleware::Next,
) -> Result<axum::response::Response, axum::http::StatusCode> {// 实现管理员权限检查逻辑next.run(req).await;Ok(())
}
🎯 实践练习
练习1:实现文章CRUD API
创建完整的文章管理API,包括:
- 获取文章列表(支持分页和搜索)
- 获取单篇文章
- 创建文章
- 更新文章
- 删除文章
练习2:实现分类管理API
创建分类管理API,包括:
- 获取分类树结构
- 创建分类
- 更新分类
- 删除分类(检查是否有关联文章)
练习3:添加请求验证
为所有API添加输入验证:
- 邮箱格式验证
- 密码强度验证
- 必填字段检查
- 数据长度限制
📚 知识点总结
Axum路由特性
- 类型安全: 编译时检查路由参数类型
- 异步支持: 原生支持async/await
- 中间件: 灵活的请求处理管道
- 模块化: 易于组织和维护
请求处理模式
- 路径参数: 使用
Path<T>
提取器 - 查询参数: 使用
Query<T>
提取器 - 请求体: 使用
Json<T>
提取器 - 响应: 统一的JSON响应格式
最佳实践
- RESTful设计: 遵循REST架构原则
- 版本控制: API版本管理策略
- 错误处理: 统一的错误响应格式
- 输入验证: 严格的参数验证
🔗 相关资源
- Axum路由文档
- Serde序列化指南
- RESTful API设计指南
- HTTP状态码参考
✅ 课后作业
- 完成所有实践练习
- 为API添加OpenAPI/Swagger文档
- 实现请求限流中间件
- 添加API响应缓存
下一堂课预告: 我们将学习错误处理和响应封装,包括统一的错误类型、自定义API错误响应格式,以及中间件的使用。