From 28afc7532f8571618db5054c7f84c455e7a2b4ee Mon Sep 17 00:00:00 2001 From: enoch Date: Mon, 4 Aug 2025 16:49:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20[=E9=98=B6=E6=AE=B51]=20=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 Cargo.toml 配置文件,包含所有必要依赖 - 建立完整的项目模块结构(config, models, handlers, routes, services, storage, middleware, utils) - 实现用户数据模型和内存存储 - 创建基础的 HTTP 处理器和路由配置 - 添加错误处理和 JWT 认证中间件 - 配置环境变量和日志系统 - 创建项目文档和学习指南 - 服务器可以成功编译和启动 --- .env.example | 9 + .gitignore | 22 ++ Cargo.toml | 44 +++ README.md | 138 +++++++ git_learning_guide.md | 121 ++++++ implementation_details.md | 719 +++++++++++++++++++++++++++++++++++ learning_guide.md | 381 +++++++++++++++++++ project_plan.md | 167 ++++++++ src/config/mod.rs | 45 +++ src/handlers/mod.rs | 22 ++ src/handlers/user.rs | 109 ++++++ src/lib.rs | 18 + src/main.rs | 35 ++ src/middleware/auth.rs | 70 ++++ src/middleware/mod.rs | 5 + src/models/mod.rs | 5 + src/models/user.rs | 72 ++++ src/routes/mod.rs | 31 ++ src/services/mod.rs | 5 + src/services/user_service.rs | 30 ++ src/storage/memory.rs | 68 ++++ src/storage/mod.rs | 5 + src/utils/errors.rs | 44 +++ src/utils/mod.rs | 5 + 24 files changed, 2170 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 git_learning_guide.md create mode 100644 implementation_details.md create mode 100644 learning_guide.md create mode 100644 project_plan.md create mode 100644 src/config/mod.rs create mode 100644 src/handlers/mod.rs create mode 100644 src/handlers/user.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/middleware/auth.rs create mode 100644 src/middleware/mod.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/user.rs create mode 100644 src/routes/mod.rs create mode 100644 src/services/mod.rs create mode 100644 src/services/user_service.rs create mode 100644 src/storage/memory.rs create mode 100644 src/storage/mod.rs create mode 100644 src/utils/errors.rs create mode 100644 src/utils/mod.rs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..084c3d3 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# 服务器配置 +SERVER_HOST=127.0.0.1 +SERVER_PORT=3000 + +# JWT 密钥 +JWT_SECRET=your-secret-key-change-this-in-production + +# 数据库配置(可选) +# DATABASE_URL=sqlite://database.db \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67a2f38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Rust +/target/ +Cargo.lock + +# IDE +.vscode/ +.idea/ + +# 环境变量 +.env + +# 日志文件 +*.log + +# 数据库文件 +*.db +*.sqlite + +# 临时文件 +*.tmp +*.swp +*~ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ea86fcc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "rust-user-api" +version = "0.1.0" +edition = "2021" +authors = ["Your Name "] +description = "A REST API server for user management built with Rust and Axum" + +[dependencies] +# Web 框架 +axum = "0.7" +tokio = { version = "1.0", features = ["full"] } +tower = "0.4" +tower-http = { version = "0.5", features = ["cors", "trace"] } + +# 序列化 +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# 时间处理 +chrono = { version = "0.4", features = ["serde"] } + +# UUID 生成 +uuid = { version = "1.0", features = ["v4", "serde"] } + +# 环境变量 +dotenv = "0.15" + +# 日志 +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# 密码哈希 +bcrypt = "0.15" + +# JWT 认证 +jsonwebtoken = "9.0" + +# 验证 +validator = { version = "0.16", features = ["derive"] } + +# HTTP 客户端(用于测试) +[dev-dependencies] +reqwest = { version = "0.11", features = ["json"] } +tokio-test = "0.4" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7d5284 --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +# Rust User API + +一个使用 Rust 和 Axum 框架构建的 REST API 服务器,用于用户管理系统的学习和演示。 + +## 功能特性 + +- ✅ 用户 CRUD 操作(创建、读取、更新、删除) +- ✅ 内存存储(HashMap) +- ✅ 请求验证和错误处理 +- ✅ JWT 身份认证 +- ✅ 结构化日志记录 +- 🔄 SQLite 数据库集成(计划中) +- 🔄 API 文档和测试(计划中) + +## 技术栈 + +- **Web 框架**: Axum 0.7 +- **异步运行时**: Tokio +- **序列化**: Serde +- **身份认证**: JWT (jsonwebtoken) +- **验证**: Validator +- **日志**: Tracing + +## 快速开始 + +### 1. 克隆项目 + +```bash +git clone +cd rust-user-api +``` + +### 2. 配置环境 + +```bash +cp .env.example .env +# 编辑 .env 文件,修改配置参数 +``` + +### 3. 运行项目 + +```bash +cargo run +``` + +服务器将在 `http://127.0.0.1:3000` 启动。 + +## API 端点 + +### 基础端点 + +- `GET /` - 欢迎信息 +- `GET /health` - 健康检查 + +### 用户管理 + +- `GET /api/users` - 获取所有用户 +- `GET /api/users/{id}` - 获取特定用户 +- `POST /api/users` - 创建新用户 +- `PUT /api/users/{id}` - 更新用户信息 +- `DELETE /api/users/{id}` - 删除用户 + +### 请求示例 + +#### 创建用户 + +```bash +curl -X POST http://localhost:3000/api/users \ + -H "Content-Type: application/json" \ + -d '{ + "username": "testuser", + "email": "test@example.com", + "password": "password123" + }' +``` + +#### 获取所有用户 + +```bash +curl http://localhost:3000/api/users +``` + +## 项目结构 + +``` +src/ +├── main.rs # 应用入口点 +├── lib.rs # 库入口 +├── config/ # 配置管理 +├── models/ # 数据模型 +├── handlers/ # HTTP 处理器 +├── routes/ # 路由配置 +├── services/ # 业务逻辑 +├── storage/ # 数据存储 +├── middleware/ # 中间件 +└── utils/ # 工具函数 +``` + +## 开发 + +### 运行测试 + +```bash +cargo test +``` + +### 检查代码 + +```bash +cargo clippy +``` + +### 格式化代码 + +```bash +cargo fmt +``` + +## 学习路径 + +这个项目按照以下阶段设计,适合渐进式学习: + +1. **基础搭建** - 项目初始化和 HTTP 服务器 +2. **数据模型** - 用户模型和内存存储 +3. **CRUD 操作** - 完整的用户管理 API +4. **错误处理** - 统一的错误处理机制 +5. **身份认证** - JWT 认证系统 +6. **测试文档** - 测试用例和 API 文档 +7. **数据库集成** - SQLite 数据库支持 +8. **高级功能** - 分页、搜索、过滤等 + +## 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 许可证 + +MIT License \ No newline at end of file diff --git a/git_learning_guide.md b/git_learning_guide.md new file mode 100644 index 0000000..c8a5648 --- /dev/null +++ b/git_learning_guide.md @@ -0,0 +1,121 @@ +# Git 学习追踪指南 + +## 概述 + +为了更好地追踪学习进度,我们将在每个 TODO 任务完成后使用 Git 创建提交记录。这样可以: + +1. 记录每个学习阶段的代码状态 +2. 方便回顾和比较不同阶段的实现 +3. 建立良好的版本控制习惯 +4. 为后续学习者提供清晰的学习路径 + +## Git 提交策略 + +### 提交消息格式 +``` +feat: [阶段X] 任务描述 + +详细说明本阶段完成的功能和学习要点 +``` + +### 学习阶段标签 +- `stage-1`: 项目初始化和基础设置 +- `stage-2`: 创建基本的 HTTP server 和路由结构 +- `stage-3`: 实现用户数据模型和内存存储 +- `stage-4`: 实现用户 CRUD API 端点 +- `stage-5`: 添加请求验证和错误处理 +- `stage-6`: 实现基础的身份认证(JWT) +- `stage-7`: 添加 API 文档和测试用例 +- `stage-8`: 扩展到 SQLite 数据库存储 +- `stage-9`: 添加数据库迁移和连接池 +- `stage-10`: 实现高级功能(分页、搜索、过滤) +- `stage-11`: 添加日志记录和配置管理 +- `stage-12`: 容器化部署准备 + +## 执行步骤 + +每完成一个 TODO 任务后: + +1. **初始化仓库**(仅第一次) + ```bash + git init + git add .gitignore + ``` + +2. **添加文件到暂存区** + ```bash + git add . + ``` + +3. **创建提交** + ```bash + git commit -m "feat: [阶段X] 任务描述" + ``` + +4. **创建标签** + ```bash + git tag -a stage-X -m "完成阶段X: 任务描述" + ``` + +## 示例提交记录 + +```bash +# 阶段 1 +git commit -m "feat: [阶段1] 项目初始化和基础设置 + +- 创建 Cargo.toml 配置文件 +- 建立项目模块结构 +- 配置依赖和开发环境 +- 创建基础配置和错误处理模块" + +git tag -a stage-1 -m "完成阶段1: 项目初始化和基础设置" + +# 阶段 2 +git commit -m "feat: [阶段2] 创建基本的 HTTP server 和路由结构 + +- 实现 main.rs 应用入口 +- 创建路由配置模块 +- 添加基础 HTTP 处理器 +- 服务器成功启动并响应请求" + +git tag -a stage-2 -m "完成阶段2: 创建基本的 HTTP server 和路由结构" +``` + +## .gitignore 文件 + +```gitignore +# Rust +/target/ +Cargo.lock + +# IDE +.vscode/ +.idea/ + +# 环境变量 +.env + +# 日志文件 +*.log + +# 数据库文件 +*.db +*.sqlite + +# 临时文件 +*.tmp +*.swp +*~ +``` + +## 学习价值 + +通过 Git 版本控制,学习者可以: + +1. **回顾学习历程**: 通过 `git log --oneline` 查看学习进度 +2. **比较代码变化**: 使用 `git diff stage-1..stage-2` 比较不同阶段 +3. **回退到特定阶段**: 使用 `git checkout stage-X` 查看特定阶段的代码 +4. **分支实验**: 创建分支尝试不同的实现方案 +5. **学习最佳实践**: 培养良好的版本控制习惯 + +这种方法不仅有助于当前的学习,也为将来的项目开发建立了良好的基础。 \ No newline at end of file diff --git a/implementation_details.md b/implementation_details.md new file mode 100644 index 0000000..67dab60 --- /dev/null +++ b/implementation_details.md @@ -0,0 +1,719 @@ +# Rust REST API Server - 详细实现指南 + +## 阶段 1: 项目初始化和基础设置 + +### 1.1 创建项目和基础配置 + +**目标**: 建立 Rust 项目基础结构和依赖管理 + +**具体步骤**: +1. 使用 `cargo new rust-user-api --bin` 创建新项目 +2. 配置 `Cargo.toml` 文件,添加必要依赖 +3. 创建基础目录结构 +4. 设置开发环境配置 + +**Cargo.toml 配置**: +```toml +[package] +name = "rust-user-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Web 框架 +axum = "0.7" +tokio = { version = "1.0", features = ["full"] } + +# 序列化 +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# 时间处理 +chrono = { version = "0.4", features = ["serde"] } + +# UUID 生成 +uuid = { version = "1.0", features = ["v4", "serde"] } + +# 环境变量 +dotenv = "0.15" + +# 日志 +tracing = "0.1" +tracing-subscriber = "0.3" + +# HTTP 客户端(用于测试) +[dev-dependencies] +reqwest = { version = "0.11", features = ["json"] } +``` + +**学习重点**: +- Cargo 包管理器的使用 +- Rust 项目结构约定 +- 依赖版本管理和特性选择 + +--- + +## 阶段 2: 创建基本的 HTTP Server 和路由结构 + +### 2.1 实现基础 HTTP 服务器 + +**目标**: 创建一个能够响应 HTTP 请求的基础服务器 + +**核心文件**: +- `src/main.rs` - 应用入口点 +- `src/lib.rs` - 库入口 +- `src/routes/mod.rs` - 路由配置 + +**main.rs 实现**: +```rust +use axum::{ + routing::get, + Router, + response::Json, +}; +use serde_json::{json, Value}; +use std::net::SocketAddr; +use tracing_subscriber; + +#[tokio::main] +async fn main() { + // 初始化日志 + tracing_subscriber::init(); + + // 创建路由 + let app = Router::new() + .route("/", get(root)) + .route("/health", get(health_check)); + + // 启动服务器 + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + println!("Server running on http://{}", addr); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +async fn root() -> Json { + Json(json!({ + "message": "Welcome to Rust User API", + "version": "0.1.0" + })) +} + +async fn health_check() -> Json { + Json(json!({ + "status": "healthy", + "timestamp": chrono::Utc::now() + })) +} +``` + +**学习重点**: +- Axum 框架基础概念 +- 异步编程 (`async/await`) +- 路由定义和处理器函数 +- JSON 响应处理 + +### 2.2 模块化路由结构 + +**routes/mod.rs 实现**: +```rust +use axum::{Router, routing::get}; +use crate::handlers; + +pub fn create_routes() -> Router { + Router::new() + .route("/", get(handlers::root)) + .route("/health", get(handlers::health_check)) + .nest("/api", api_routes()) +} + +fn api_routes() -> Router { + Router::new() + .route("/users", get(handlers::user::list_users)) + // 后续添加更多路由 +} +``` + +**学习重点**: +- 模块系统和代码组织 +- 路由嵌套和分组 +- 处理器函数的分离 + +--- + +## 阶段 3: 实现用户数据模型和内存存储 + +### 3.1 定义数据模型 + +**目标**: 创建类型安全的数据模型和序列化支持 + +**models/user.rs 实现**: +```rust +use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; +use uuid::Uuid; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct User { + pub id: Uuid, + pub username: String, + pub email: String, + pub password_hash: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Serialize)] +pub struct UserResponse { + pub id: Uuid, + pub username: String, + pub email: String, + pub created_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub struct CreateUserRequest { + pub username: String, + pub email: String, + pub password: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateUserRequest { + pub username: Option, + pub email: Option, +} + +impl From for UserResponse { + fn from(user: User) -> Self { + UserResponse { + id: user.id, + username: user.username, + email: user.email, + created_at: user.created_at, + } + } +} +``` + +**学习重点**: +- Rust 结构体定义 +- Serde 序列化和反序列化 +- 类型转换和 `From` trait +- 可选字段处理 + +### 3.2 实现内存存储 + +**storage/memory.rs 实现**: +```rust +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; +use uuid::Uuid; +use crate::models::user::User; + +pub type UserStorage = Arc>>; + +#[derive(Clone)] +pub struct MemoryUserStore { + users: UserStorage, +} + +impl MemoryUserStore { + pub fn new() -> Self { + Self { + users: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub async fn create_user(&self, user: User) -> Result { + let mut users = self.users.write().unwrap(); + users.insert(user.id, user.clone()); + Ok(user) + } + + pub async fn get_user(&self, id: &Uuid) -> Option { + let users = self.users.read().unwrap(); + users.get(id).cloned() + } + + pub async fn list_users(&self) -> Vec { + let users = self.users.read().unwrap(); + users.values().cloned().collect() + } + + pub async fn update_user(&self, id: &Uuid, updated_user: User) -> Option { + let mut users = self.users.write().unwrap(); + users.insert(*id, updated_user.clone()); + Some(updated_user) + } + + pub async fn delete_user(&self, id: &Uuid) -> bool { + let mut users = self.users.write().unwrap(); + users.remove(id).is_some() + } +} +``` + +**学习重点**: +- HashMap 数据结构 +- 线程安全 (`Arc>`) +- 异步方法定义 +- 错误处理基础 + +--- + +## 阶段 4: 实现用户 CRUD API 端点 + +### 4.1 创建处理器函数 + +**handlers/user.rs 实现**: +```rust +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::Json, + Json as RequestJson, +}; +use uuid::Uuid; +use chrono::Utc; + +use crate::models::user::{User, UserResponse, CreateUserRequest, UpdateUserRequest}; +use crate::storage::memory::MemoryUserStore; + +pub async fn create_user( + State(store): State, + RequestJson(payload): RequestJson, +) -> Result<(StatusCode, Json), StatusCode> { + let user = User { + id: Uuid::new_v4(), + username: payload.username, + email: payload.email, + password_hash: hash_password(&payload.password), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + match store.create_user(user).await { + Ok(user) => Ok((StatusCode::CREATED, Json(user.into()))), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +pub async fn get_user( + State(store): State, + Path(id): Path, +) -> Result, StatusCode> { + match store.get_user(&id).await { + Some(user) => Ok(Json(user.into())), + None => Err(StatusCode::NOT_FOUND), + } +} + +pub async fn list_users( + State(store): State, +) -> Json> { + let users = store.list_users().await; + let responses: Vec = users.into_iter().map(|u| u.into()).collect(); + Json(responses) +} + +pub async fn update_user( + State(store): State, + Path(id): Path, + RequestJson(payload): RequestJson, +) -> Result, StatusCode> { + match store.get_user(&id).await { + Some(mut user) => { + if let Some(username) = payload.username { + user.username = username; + } + if let Some(email) = payload.email { + user.email = email; + } + user.updated_at = Utc::now(); + + match store.update_user(&id, user).await { + Some(updated_user) => Ok(Json(updated_user.into())), + None => Err(StatusCode::INTERNAL_SERVER_ERROR), + } + } + None => Err(StatusCode::NOT_FOUND), + } +} + +pub async fn delete_user( + State(store): State, + Path(id): Path, +) -> StatusCode { + if store.delete_user(&id).await { + StatusCode::NO_CONTENT + } else { + StatusCode::NOT_FOUND + } +} + +fn hash_password(password: &str) -> String { + // 简单的密码哈希(生产环境应使用 bcrypt) + format!("hashed_{}", password) +} +``` + +**学习重点**: +- Axum 提取器 (`State`, `Path`, `Json`) +- HTTP 状态码处理 +- 错误处理和 `Result` 类型 +- 异步处理器函数 + +### 4.2 完整路由配置 + +**更新 routes/mod.rs**: +```rust +use axum::{ + Router, + routing::{get, post, put, delete}, +}; +use crate::handlers; +use crate::storage::memory::MemoryUserStore; + +pub fn create_routes(store: MemoryUserStore) -> Router { + Router::new() + .route("/", get(handlers::root)) + .route("/health", get(handlers::health_check)) + .nest("/api", api_routes()) + .with_state(store) +} + +fn api_routes() -> Router { + Router::new() + .route("/users", get(handlers::user::list_users).post(handlers::user::create_user)) + .route("/users/:id", + get(handlers::user::get_user) + .put(handlers::user::update_user) + .delete(handlers::user::delete_user) + ) +} +``` + +**学习重点**: +- 状态管理和依赖注入 +- RESTful 路由设计 +- HTTP 方法映射 + +--- + +## 阶段 5: 添加请求验证和错误处理 + +### 5.1 自定义错误类型 + +**utils/errors.rs 实现**: +```rust +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + Json, +}; +use serde_json::json; + +#[derive(Debug)] +pub enum ApiError { + ValidationError(String), + NotFound(String), + InternalError(String), + Unauthorized, +} + +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + let (status, error_message) = match self { + ApiError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg), + ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg), + ApiError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), + ApiError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_string()), + }; + + let body = Json(json!({ + "error": error_message, + "status": status.as_u16() + })); + + (status, body).into_response() + } +} +``` + +### 5.2 请求验证 + +**添加验证逻辑**: +```rust +use validator::{Validate, ValidationError}; + +#[derive(Debug, Deserialize, Validate)] +pub struct CreateUserRequest { + #[validate(length(min = 3, max = 50))] + pub username: String, + + #[validate(email)] + pub email: String, + + #[validate(length(min = 8))] + pub password: String, +} + +// 在处理器中使用验证 +pub async fn create_user( + State(store): State, + RequestJson(payload): RequestJson, +) -> Result<(StatusCode, Json), ApiError> { + // 验证请求数据 + payload.validate() + .map_err(|e| ApiError::ValidationError(format!("Validation failed: {:?}", e)))?; + + // ... 其余逻辑 +} +``` + +**学习重点**: +- 自定义错误类型 +- `IntoResponse` trait 实现 +- 请求验证和数据校验 +- 错误传播 (`?` 操作符) + +--- + +## 阶段 6: 实现基础的身份认证 (JWT) + +### 6.1 JWT 中间件 + +**middleware/auth.rs 实现**: +```rust +use axum::{ + extract::{Request, State}, + http::{header, StatusCode}, + middleware::Next, + response::Response, +}; +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub sub: String, // 用户ID + pub exp: usize, // 过期时间 +} + +pub async fn auth_middleware( + mut req: Request, + next: Next, +) -> Result { + let auth_header = req.headers() + .get(header::AUTHORIZATION) + .and_then(|header| header.to_str().ok()); + + let auth_header = if let Some(auth_header) = auth_header { + auth_header + } else { + return Err(StatusCode::UNAUTHORIZED); + }; + + if let Some(token) = auth_header.strip_prefix("Bearer ") { + match verify_jwt(token) { + Ok(claims) => { + req.extensions_mut().insert(claims); + Ok(next.run(req).await) + } + Err(_) => Err(StatusCode::UNAUTHORIZED), + } + } else { + Err(StatusCode::UNAUTHORIZED) + } +} + +fn verify_jwt(token: &str) -> Result { + let key = DecodingKey::from_secret("secret".as_ref()); + let validation = Validation::default(); + + decode::(token, &key, &validation) + .map(|data| data.claims) +} + +pub fn create_jwt(user_id: &str) -> Result { + 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(), + exp: expiration, + }; + + let key = EncodingKey::from_secret("secret".as_ref()); + encode(&Header::default(), &claims, &key) +} +``` + +### 6.2 登录端点 + +**添加认证处理器**: +```rust +#[derive(Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Serialize)] +pub struct LoginResponse { + pub token: String, + pub user: UserResponse, +} + +pub async fn login( + State(store): State, + RequestJson(payload): RequestJson, +) -> Result, ApiError> { + // 查找用户(简化版本,实际应该按用户名查找) + let users = store.list_users().await; + let user = users.into_iter() + .find(|u| u.username == payload.username) + .ok_or_else(|| ApiError::NotFound("User not found".to_string()))?; + + // 验证密码(简化版本) + if user.password_hash != format!("hashed_{}", payload.password) { + return Err(ApiError::Unauthorized); + } + + // 生成 JWT + let token = create_jwt(&user.id.to_string()) + .map_err(|_| ApiError::InternalError("Failed to create token".to_string()))?; + + Ok(Json(LoginResponse { + token, + user: user.into(), + })) +} +``` + +**学习重点**: +- JWT 概念和实现 +- 中间件设计模式 +- 请求扩展和状态传递 +- 身份认证流程 + +--- + +## 阶段 7: 添加 API 文档和测试用例 + +### 7.1 集成测试 + +**tests/api_tests.rs 实现**: +```rust +use reqwest; +use serde_json::json; +use tokio; + +#[tokio::test] +async fn test_create_user() { + let client = reqwest::Client::new(); + + let user_data = json!({ + "username": "testuser", + "email": "test@example.com", + "password": "password123" + }); + + let response = client + .post("http://localhost:3000/api/users") + .json(&user_data) + .send() + .await + .expect("Failed to send request"); + + assert_eq!(response.status(), 201); + + let user: serde_json::Value = response.json().await.expect("Failed to parse JSON"); + assert_eq!(user["username"], "testuser"); + assert_eq!(user["email"], "test@example.com"); +} + +#[tokio::test] +async fn test_get_user() { + // 首先创建用户,然后获取 + // ... 测试逻辑 +} +``` + +### 7.2 API 文档 + +**docs/api.md**: +```markdown +# User API Documentation + +## Authentication +All protected endpoints require a Bearer token in the Authorization header: +``` +Authorization: Bearer +``` + +## Endpoints + +### POST /api/auth/login +Login with username and password. + +**Request Body:** +```json +{ + "username": "string", + "password": "string" +} +``` + +**Response:** +```json +{ + "token": "jwt_token_string", + "user": { + "id": "uuid", + "username": "string", + "email": "string", + "created_at": "timestamp" + } +} +``` + +### GET /api/users +Get all users (requires authentication). + +**Response:** +```json +[ + { + "id": "uuid", + "username": "string", + "email": "string", + "created_at": "timestamp" + } +] +``` +``` + +**学习重点**: +- 集成测试编写 +- HTTP 客户端测试 +- API 文档编写规范 +- 测试驱动开发 + +--- + +## 后续阶段概览 + +### 阶段 8-12: 高级功能 +- **数据库集成**: SQLite/PostgreSQL 集成,ORM 使用 +- **高级功能**: 分页、搜索、过滤、排序 +- **生产准备**: 配置管理、日志记录、错误监控 +- **部署**: Docker 容器化、环境配置 + +每个阶段都建立在前一个阶段的基础上,确保学习的连续性和实用性。通过这种渐进式的方法,您将全面掌握 Rust web 开发的各个方面。 \ No newline at end of file diff --git a/learning_guide.md b/learning_guide.md new file mode 100644 index 0000000..d3d8e7f --- /dev/null +++ b/learning_guide.md @@ -0,0 +1,381 @@ +# Rust REST API 学习指南 + +## 学习前准备 + +### 必备知识 +- Rust 基础语法(所有权、借用、生命周期) +- 基本的 HTTP 协议知识 +- REST API 设计原则 +- JSON 数据格式 + +### 开发环境 +- Rust 1.70+ +- VS Code 或其他 IDE +- Postman 或 curl(API 测试) +- Git(版本控制) + +## 详细学习路径 + +### 第一周:基础搭建(阶段 1-2) + +#### 学习目标 +- 掌握 Cargo 项目管理 +- 理解 Axum 框架基础 +- 学会创建基本的 HTTP 服务 + +#### 关键概念 +1. **Cargo.toml 配置** + - 依赖管理和版本控制 + - 特性(features)选择 + - 开发依赖 vs 运行时依赖 + +2. **异步编程基础** + - `async/await` 语法 + - Tokio 运行时 + - Future 和 Task 概念 + +3. **Axum 框架核心** + - Router 和路由定义 + - 处理器函数(Handler) + - 响应类型和状态码 + +#### 实践练习 +```rust +// 练习1: 创建健康检查端点 +async fn health() -> Json { + Json(json!({ + "status": "ok", + "timestamp": Utc::now(), + "uptime": "1h 30m" + })) +} + +// 练习2: 添加版本信息端点 +async fn version() -> Json { + Json(json!({ + "version": env!("CARGO_PKG_VERSION"), + "name": env!("CARGO_PKG_NAME") + })) +} +``` + +#### 常见问题和解决方案 +- **编译错误**: 检查依赖版本兼容性 +- **异步函数理解**: 从同步思维转向异步思维 +- **模块导入**: 理解 Rust 的模块系统 + +--- + +### 第二周:数据模型和存储(阶段 3-4) + +#### 学习目标 +- 掌握 Serde 序列化 +- 理解内存存储设计 +- 实现完整的 CRUD 操作 + +#### 关键概念 +1. **数据建模** + - 结构体设计原则 + - 序列化和反序列化 + - 类型转换和 From trait + +2. **并发安全** + - `Arc>` 模式 + - 读写锁的使用场景 + - 线程安全的数据共享 + +3. **HTTP 方法映射** + - GET, POST, PUT, DELETE + - RESTful 设计原则 + - 路径参数提取 + +#### 实践练习 +```rust +// 练习1: 扩展用户模型 +#[derive(Debug, Serialize, Deserialize)] +pub struct User { + pub id: Uuid, + pub username: String, + pub email: String, + pub profile: UserProfile, + pub status: UserStatus, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserProfile { + pub first_name: String, + pub last_name: String, + pub bio: Option, +} + +// 练习2: 实现用户搜索 +pub async fn search_users( + State(store): State, + Query(params): Query, +) -> Json> { + // 实现按用户名或邮箱搜索 +} +``` + +#### 调试技巧 +- 使用 `println!` 和 `dbg!` 宏调试 +- 理解借用检查器错误信息 +- 使用 Postman 测试 API 端点 + +--- + +### 第三周:错误处理和验证(阶段 5) + +#### 学习目标 +- 掌握 Rust 错误处理模式 +- 实现请求数据验证 +- 创建统一的错误响应 + +#### 关键概念 +1. **错误处理哲学** + - `Result` 类型 + - `?` 操作符的使用 + - 错误传播和转换 + +2. **自定义错误类型** + - 枚举定义错误类型 + - `IntoResponse` trait 实现 + - 错误信息的用户友好化 + +3. **数据验证** + - 输入验证策略 + - 自定义验证规则 + - 验证错误的处理 + +#### 实践练习 +```rust +// 练习1: 扩展错误类型 +#[derive(Debug)] +pub enum ApiError { + ValidationError(ValidationErrors), + DatabaseError(String), + NotFound(String), + Conflict(String), + Unauthorized, + Forbidden, +} + +// 练习2: 实现自定义验证器 +fn validate_username(username: &str) -> Result<(), ValidationError> { + if username.len() < 3 { + return Err(ValidationError::new("Username too short")); + } + if username.contains(' ') { + return Err(ValidationError::new("Username cannot contain spaces")); + } + Ok(()) +} +``` + +#### 最佳实践 +- 永远不要 `unwrap()` 在生产代码中 +- 提供有意义的错误信息 +- 区分内部错误和用户错误 + +--- + +### 第四周:身份认证(阶段 6) + +#### 学习目标 +- 理解 JWT 工作原理 +- 实现中间件模式 +- 掌握安全编程基础 + +#### 关键概念 +1. **JWT 认证流程** + - Token 生成和验证 + - Claims 和 Payload + - 安全密钥管理 + +2. **中间件设计** + - 请求拦截和处理 + - 状态传递机制 + - 条件性中间件应用 + +3. **安全考虑** + - 密码哈希(bcrypt) + - Token 过期处理 + - HTTPS 和安全头 + +#### 实践练习 +```rust +// 练习1: 实现角色基础访问控制 +#[derive(Debug, Serialize, Deserialize)] +pub enum UserRole { + Admin, + User, + Guest, +} + +pub async fn admin_only_middleware( + claims: Claims, + req: Request, + next: Next, +) -> Result { + if claims.role != UserRole::Admin { + return Err(StatusCode::FORBIDDEN); + } + Ok(next.run(req).await) +} + +// 练习2: 实现刷新 Token 机制 +pub async fn refresh_token( + State(store): State, + RequestJson(payload): RequestJson, +) -> Result, ApiError> { + // 验证刷新 token 并生成新的访问 token +} +``` + +#### 安全检查清单 +- [ ] 使用强密码哈希算法 +- [ ] 实现 Token 过期机制 +- [ ] 验证所有输入数据 +- [ ] 记录安全相关事件 + +--- + +### 第五周:测试和文档(阶段 7) + +#### 学习目标 +- 编写全面的测试套件 +- 创建清晰的 API 文档 +- 掌握测试驱动开发 + +#### 关键概念 +1. **测试策略** + - 单元测试 vs 集成测试 + - 测试数据管理 + - Mock 和 Stub 技术 + +2. **API 测试** + - HTTP 客户端测试 + - 异步测试编写 + - 测试环境隔离 + +3. **文档编写** + - OpenAPI/Swagger 规范 + - 代码注释最佳实践 + - 示例和用例 + +#### 实践练习 +```rust +// 练习1: 编写完整的用户 CRUD 测试 +#[tokio::test] +async fn test_user_lifecycle() { + let app = create_test_app().await; + + // 创建用户 + let user = create_test_user(&app, "testuser").await; + assert_eq!(user.username, "testuser"); + + // 获取用户 + let fetched_user = get_user(&app, user.id).await; + assert_eq!(fetched_user.id, user.id); + + // 更新用户 + let updated_user = update_user(&app, user.id, "newname").await; + assert_eq!(updated_user.username, "newname"); + + // 删除用户 + delete_user(&app, user.id).await; + assert!(get_user(&app, user.id).await.is_err()); +} + +// 练习2: 性能测试 +#[tokio::test] +async fn test_concurrent_user_creation() { + let app = create_test_app().await; + let mut handles = vec![]; + + for i in 0..100 { + let app_clone = app.clone(); + let handle = tokio::spawn(async move { + create_test_user(&app_clone, &format!("user{}", i)).await + }); + handles.push(handle); + } + + let results = futures::future::join_all(handles).await; + assert_eq!(results.len(), 100); +} +``` + +--- + +### 第六周及以后:高级功能(阶段 8-12) + +#### 数据库集成(阶段 8-9) +- SQLite 和 PostgreSQL 集成 +- ORM 选择(Diesel vs SQLx) +- 数据库迁移管理 +- 连接池配置 + +#### 高级功能(阶段 10) +- 分页实现 +- 搜索和过滤 +- 排序和聚合 +- 缓存策略 + +#### 生产准备(阶段 11-12) +- 配置管理(环境变量) +- 结构化日志记录 +- 监控和指标收集 +- Docker 容器化 +- 部署策略 + +## 学习资源推荐 + +### 官方文档 +- [Rust Book](https://doc.rust-lang.org/book/) +- [Axum Documentation](https://docs.rs/axum/) +- [Tokio Tutorial](https://tokio.rs/tokio/tutorial) + +### 实用工具 +- [Postman](https://www.postman.com/) - API 测试 +- [SQLite Browser](https://sqlitebrowser.org/) - 数据库管理 +- [Docker](https://www.docker.com/) - 容器化 + +### 社区资源 +- [Rust Users Forum](https://users.rust-lang.org/) +- [r/rust](https://www.reddit.com/r/rust/) +- [Rust Discord](https://discord.gg/rust-lang) + +## 常见问题解答 + +### Q: 为什么选择 Axum 而不是其他框架? +A: Axum 是由 Tokio 团队开发的现代 web 框架,具有以下优势: +- 类型安全的提取器系统 +- 优秀的性能表现 +- 与 Tokio 生态系统深度集成 +- 活跃的社区支持 + +### Q: 内存存储什么时候应该迁移到数据库? +A: 当您需要以下功能时: +- 数据持久化 +- 复杂查询 +- 事务支持 +- 多实例部署 + +### Q: 如何处理大量并发请求? +A: 考虑以下策略: +- 使用连接池 +- 实现请求限流 +- 添加缓存层 +- 优化数据库查询 +- 使用负载均衡 + +### Q: 生产环境部署需要注意什么? +A: 关键考虑因素: +- 环境配置管理 +- 日志记录和监控 +- 安全性(HTTPS、防火墙) +- 备份和恢复策略 +- 性能监控 + +通过这个详细的学习指南,您将能够系统性地掌握 Rust web 开发的各个方面,从基础概念到生产级应用开发。 \ No newline at end of file diff --git a/project_plan.md b/project_plan.md new file mode 100644 index 0000000..728d91b --- /dev/null +++ b/project_plan.md @@ -0,0 +1,167 @@ +# Rust REST API Server Demo 项目规划 + +## 项目概述 + +这是一个专为学习 Rust web 开发设计的渐进式 REST API server 项目。项目从简单的内存存储开始,逐步扩展到数据库集成和高级功能,帮助您全面掌握 Rust web 开发的核心概念。 + +## 技术栈 + +- **Web 框架**: Axum(现代、高性能、类型安全) +- **序列化**: Serde(JSON 处理) +- **异步运行时**: Tokio(异步编程基础) +- **数据库**: 先用 HashMap(内存),后扩展到 SQLite +- **身份认证**: JWT tokens +- **测试**: 内置测试框架 + reqwest(HTTP 客户端测试) + +## 项目结构 + +``` +rust-user-api/ +├── Cargo.toml # 项目配置和依赖 +├── README.md # 项目说明文档 +├── src/ +│ ├── main.rs # 应用入口点 +│ ├── lib.rs # 库入口,导出公共模块 +│ ├── config/ +│ │ └── mod.rs # 配置管理 +│ ├── models/ +│ │ ├── mod.rs # 数据模型模块 +│ │ └── user.rs # 用户数据模型 +│ ├── handlers/ +│ │ ├── mod.rs # 处理器模块 +│ │ └── user.rs # 用户相关的 API 处理器 +│ ├── services/ +│ │ ├── mod.rs # 业务逻辑服务模块 +│ │ └── user_service.rs # 用户业务逻辑 +│ ├── storage/ +│ │ ├── mod.rs # 存储抽象模块 +│ │ ├── memory.rs # 内存存储实现 +│ │ └── database.rs # 数据库存储实现(后期) +│ ├── middleware/ +│ │ ├── mod.rs # 中间件模块 +│ │ └── auth.rs # 身份认证中间件 +│ ├── utils/ +│ │ ├── mod.rs # 工具函数模块 +│ │ └── errors.rs # 错误处理 +│ └── routes/ +│ └── mod.rs # 路由配置 +├── tests/ +│ ├── integration_tests.rs # 集成测试 +│ └── api_tests.rs # API 测试 +└── docs/ + └── api.md # API 文档 +``` + +## API 端点设计 + +### 用户管理 API +``` +GET /api/users # 获取所有用户(支持分页) +GET /api/users/{id} # 获取特定用户 +POST /api/users # 创建新用户 +PUT /api/users/{id} # 更新用户信息 +DELETE /api/users/{id} # 删除用户 +POST /api/auth/login # 用户登录 +POST /api/auth/register # 用户注册 +``` + +## 数据模型设计 + +### 用户模型 +```rust +pub struct User { + pub id: u64, + pub username: String, + pub email: String, + pub password_hash: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +pub struct UserResponse { + pub id: u64, + pub username: String, + pub email: String, + pub created_at: DateTime, +} +``` + +## 学习路径 + +### 阶段 1: 基础搭建 +- **目标**: 建立项目基础,理解 Rust 项目结构 +- **重点学习**: Cargo 项目管理、模块系统、基础 HTTP 服务 + +### 阶段 2: 核心功能实现 +- **目标**: 实现完整的 CRUD 操作 +- **重点学习**: Axum 框架、异步编程、JSON 处理 + +### 阶段 3: 错误处理和验证 +- **目标**: 添加健壮的错误处理机制 +- **重点学习**: Rust 错误处理最佳实践、请求验证 + +### 阶段 4: 身份认证 +- **目标**: 实现 JWT 身份认证系统 +- **重点学习**: 安全编程、中间件设计 + +### 阶段 5: 测试和文档 +- **目标**: 完善测试覆盖和 API 文档 +- **重点学习**: 单元测试、集成测试、API 文档编写 + +### 阶段 6: 数据持久化 +- **目标**: 从内存存储迁移到数据库 +- **重点学习**: 数据库集成、迁移管理、连接池 + +### 阶段 7: 高级功能 +- **目标**: 添加分页、搜索、过滤等功能 +- **重点学习**: 性能优化、复杂查询处理 + +### 阶段 8: 生产准备 +- **目标**: 添加日志、配置管理、容器化 +- **重点学习**: 生产环境最佳实践、部署策略 + +## 架构设计 + +```mermaid +graph TB + Client[客户端] --> Router[路由层] + Router --> Auth[认证中间件] + Auth --> Handler[处理器层] + Handler --> Service[业务逻辑层] + Service --> Storage[存储层] + + Storage --> Memory[内存存储] + Storage --> Database[数据库存储] + + Handler --> Response[响应处理] + Response --> Client + + subgraph "错误处理" + ErrorHandler[错误处理器] + Handler --> ErrorHandler + Service --> ErrorHandler + Storage --> ErrorHandler + end +``` + +## 项目优势 + +1. **渐进式学习**: 从简单开始,逐步增加复杂性 +2. **模块化架构**: 清晰的分层设计,便于理解和维护 +3. **实用性强**: 涵盖真实项目中的常见需求 +4. **最佳实践**: 遵循 Rust 社区的最佳实践和惯例 +5. **完整覆盖**: 从基础语法到生产部署的全流程学习 + +## 预期学习成果 + +完成这个项目后,您将掌握: +- Rust 项目组织和模块系统 +- 异步编程和 Tokio 运行时 +- Web 框架(Axum)的使用 +- REST API 设计和实现 +- 数据库集成和 ORM 使用 +- 身份认证和安全编程 +- 错误处理和测试编写 +- 生产环境部署准备 + +这个项目将为您提供一个完整的 Rust web 开发学习体验,帮助您从初学者成长为能够独立开发 Rust web 应用的开发者。 \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..7005fca --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,45 @@ +//! Configuration management module +//! +//! This module handles application configuration including +//! server settings, database connections, and environment variables. + +use std::env; + +#[derive(Debug, Clone)] +pub struct Config { + pub server_host: String, + pub server_port: u16, + pub jwt_secret: String, + pub database_url: Option, +} + +impl Config { + pub fn from_env() -> Self { + dotenv::dotenv().ok(); + + Self { + server_host: env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()), + server_port: env::var("SERVER_PORT") + .unwrap_or_else(|_| "3000".to_string()) + .parse() + .expect("SERVER_PORT must be a valid number"), + jwt_secret: env::var("JWT_SECRET").unwrap_or_else(|_| "your-secret-key".to_string()), + database_url: env::var("DATABASE_URL").ok(), + } + } + + pub fn server_address(&self) -> String { + format!("{}:{}", self.server_host, self.server_port) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + server_host: "127.0.0.1".to_string(), + server_port: 3000, + jwt_secret: "your-secret-key".to_string(), + database_url: None, + } + } +} \ No newline at end of file diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..2394ffe --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,22 @@ +//! HTTP 请求处理器模块 + +pub mod user; + +use axum::{response::Json, http::StatusCode}; +use serde_json::{json, Value}; + +/// 根路径处理器 +pub async fn root() -> Json { + Json(json!({ + "message": "欢迎使用 Rust User API", + "version": "0.1.0" + })) +} + +/// 健康检查处理器 +pub async fn health_check() -> (StatusCode, Json) { + (StatusCode::OK, Json(json!({ + "status": "healthy", + "timestamp": chrono::Utc::now() + }))) +} \ No newline at end of file diff --git a/src/handlers/user.rs b/src/handlers/user.rs new file mode 100644 index 0000000..53e2eb0 --- /dev/null +++ b/src/handlers/user.rs @@ -0,0 +1,109 @@ +//! 用户相关的 HTTP 处理器 + +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::Json, + Json as RequestJson, +}; +use uuid::Uuid; +use chrono::Utc; +use validator::Validate; + +use crate::models::user::{User, UserResponse, CreateUserRequest, UpdateUserRequest}; +use crate::storage::memory::MemoryUserStore; +use crate::utils::errors::ApiError; + +/// 创建用户 +pub async fn create_user( + State(store): State, + RequestJson(payload): RequestJson, +) -> Result<(StatusCode, Json), ApiError> { + // 验证请求数据 + payload.validate()?; + + // 检查用户名是否已存在 + if store.get_user_by_username(&payload.username).await.is_some() { + return Err(ApiError::Conflict("用户名已存在".to_string())); + } + + // 创建新用户 + let user = User { + id: Uuid::new_v4(), + username: payload.username, + email: payload.email, + password_hash: hash_password(&payload.password), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + match store.create_user(user).await { + Ok(user) => Ok((StatusCode::CREATED, Json(user.into()))), + Err(e) => Err(ApiError::InternalError(e)), + } +} + +/// 获取单个用户 +pub async fn get_user( + State(store): State, + Path(id): Path, +) -> Result, ApiError> { + match store.get_user(&id).await { + Some(user) => Ok(Json(user.into())), + None => Err(ApiError::NotFound("用户不存在".to_string())), + } +} + +/// 获取所有用户 +pub async fn list_users( + State(store): State, +) -> Json> { + let users = store.list_users().await; + let responses: Vec = users.into_iter().map(|u| u.into()).collect(); + Json(responses) +} + +/// 更新用户 +pub async fn update_user( + State(store): State, + Path(id): Path, + RequestJson(payload): RequestJson, +) -> Result, ApiError> { + // 验证请求数据 + payload.validate()?; + + match store.get_user(&id).await { + Some(mut user) => { + if let Some(username) = payload.username { + user.username = username; + } + if let Some(email) = payload.email { + user.email = email; + } + user.updated_at = Utc::now(); + + match store.update_user(&id, user).await { + Some(updated_user) => Ok(Json(updated_user.into())), + None => Err(ApiError::InternalError("更新用户失败".to_string())), + } + } + None => Err(ApiError::NotFound("用户不存在".to_string())), + } +} + +/// 删除用户 +pub async fn delete_user( + State(store): State, + Path(id): Path, +) -> Result { + if store.delete_user(&id).await { + Ok(StatusCode::NO_CONTENT) + } else { + Err(ApiError::NotFound("用户不存在".to_string())) + } +} + +/// 简单的密码哈希函数(生产环境应使用 bcrypt) +fn hash_password(password: &str) -> String { + format!("hashed_{}", password) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..183904e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +//! Rust User API - A REST API server for user management +//! +//! This library provides a complete REST API server implementation +//! with user management, authentication, and CRUD operations. + +pub mod config; +pub mod handlers; +pub mod middleware; +pub mod models; +pub mod routes; +pub mod services; +pub mod storage; +pub mod utils; + +// Re-export commonly used types +pub use models::user::{User, UserResponse, CreateUserRequest, UpdateUserRequest}; +pub use storage::memory::MemoryUserStore; +pub use utils::errors::ApiError; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e62c0ce --- /dev/null +++ b/src/main.rs @@ -0,0 +1,35 @@ +//! Rust User API 服务器主程序 + +use std::net::SocketAddr; +use tracing_subscriber; +use rust_user_api::{ + config::Config, + routes::create_routes, + storage::memory::MemoryUserStore, +}; + +#[tokio::main] +async fn main() { + // 初始化日志 + tracing_subscriber::fmt::init(); + + // 加载配置 + let config = Config::from_env(); + + // 创建存储实例 + let store = MemoryUserStore::new(); + + // 创建路由 + let app = create_routes(store); + + // 启动服务器 + let addr: SocketAddr = config.server_address().parse() + .expect("无效的服务器地址"); + + println!("🚀 服务器启动在 http://{}", addr); + println!("📚 API 文档: http://{}/", addr); + println!("❤️ 健康检查: http://{}/health", addr); + + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} \ No newline at end of file diff --git a/src/middleware/auth.rs b/src/middleware/auth.rs new file mode 100644 index 0000000..6b1d438 --- /dev/null +++ b/src/middleware/auth.rs @@ -0,0 +1,70 @@ +//! JWT 认证中间件 + +use axum::{ + extract::Request, + http::{header, StatusCode}, + middleware::Next, + response::Response, +}; +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; + +/// JWT Claims +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Claims { + pub sub: String, // 用户ID + pub exp: usize, // 过期时间 +} + +/// JWT 认证中间件 +pub async fn auth_middleware( + mut req: Request, + next: Next, +) -> Result { + let auth_header = req.headers() + .get(header::AUTHORIZATION) + .and_then(|header| header.to_str().ok()); + + let auth_header = if let Some(auth_header) = auth_header { + auth_header + } else { + return Err(StatusCode::UNAUTHORIZED); + }; + + if let Some(token) = auth_header.strip_prefix("Bearer ") { + match verify_jwt(token) { + Ok(claims) => { + req.extensions_mut().insert(claims); + Ok(next.run(req).await) + } + Err(_) => Err(StatusCode::UNAUTHORIZED), + } + } else { + Err(StatusCode::UNAUTHORIZED) + } +} + +/// 验证 JWT token +fn verify_jwt(token: &str) -> Result { + let key = DecodingKey::from_secret("your-secret-key".as_ref()); + let validation = Validation::default(); + + decode::(token, &key, &validation) + .map(|data| data.claims) +} + +/// 创建 JWT token +pub fn create_jwt(user_id: &str) -> Result { + 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(), + exp: expiration, + }; + + let key = EncodingKey::from_secret("your-secret-key".as_ref()); + encode(&Header::default(), &claims, &key) +} \ No newline at end of file diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 0000000..d63338c --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1,5 @@ +//! 中间件模块 + +pub mod auth; + +pub use auth::{auth_middleware, Claims}; \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..19b75c1 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,5 @@ +//! 数据模型模块 + +pub mod user; + +pub use user::{User, UserResponse, CreateUserRequest, UpdateUserRequest, LoginRequest, LoginResponse}; \ No newline at end of file diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..7ee409e --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,72 @@ +//! 用户数据模型 + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use validator::Validate; + +/// 用户实体 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct User { + pub id: Uuid, + pub username: String, + pub email: String, + pub password_hash: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// 用户响应(不包含敏感信息) +#[derive(Debug, Serialize)] +pub struct UserResponse { + pub id: Uuid, + pub username: String, + pub email: String, + pub created_at: DateTime, +} + +/// 创建用户请求 +#[derive(Debug, Deserialize, Validate)] +pub struct CreateUserRequest { + #[validate(length(min = 3, max = 50))] + pub username: String, + #[validate(email)] + pub email: String, + #[validate(length(min = 8))] + pub password: String, +} + +/// 更新用户请求 +#[derive(Debug, Deserialize, Validate)] +pub struct UpdateUserRequest { + #[validate(length(min = 3, max = 50))] + pub username: Option, + #[validate(email)] + pub email: Option, +} + +/// 登录请求 +#[derive(Debug, Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +/// 登录响应 +#[derive(Debug, Serialize)] +pub struct LoginResponse { + pub token: String, + pub user: UserResponse, +} + +/// 从 User 转换为 UserResponse +impl From for UserResponse { + fn from(user: User) -> Self { + UserResponse { + id: user.id, + username: user.username, + email: user.email, + created_at: user.created_at, + } + } +} \ No newline at end of file diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..a14a158 --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,31 @@ +//! 路由配置模块 + +use axum::{ + Router, + routing::{get, post, put, delete}, +}; +use crate::handlers; +use crate::storage::memory::MemoryUserStore; + +/// 创建应用路由 +pub fn create_routes(store: MemoryUserStore) -> Router { + Router::new() + .route("/", get(handlers::root)) + .route("/health", get(handlers::health_check)) + .nest("/api", api_routes()) + .with_state(store) +} + +/// API 路由 +fn api_routes() -> Router { + Router::new() + .route("/users", + get(handlers::user::list_users) + .post(handlers::user::create_user) + ) + .route("/users/:id", + get(handlers::user::get_user) + .put(handlers::user::update_user) + .delete(handlers::user::delete_user) + ) +} \ No newline at end of file diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..6dedf1e --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,5 @@ +//! 业务逻辑服务模块 + +pub mod user_service; + +pub use user_service::UserService; \ No newline at end of file diff --git a/src/services/user_service.rs b/src/services/user_service.rs new file mode 100644 index 0000000..2c1a966 --- /dev/null +++ b/src/services/user_service.rs @@ -0,0 +1,30 @@ +//! 用户业务逻辑服务 + +use crate::models::user::User; +use crate::storage::memory::MemoryUserStore; +use crate::utils::errors::ApiError; + +/// 用户服务 +pub struct UserService { + store: MemoryUserStore, +} + +impl UserService { + pub fn new(store: MemoryUserStore) -> Self { + Self { store } + } + + /// 验证用户凭据 + pub async fn authenticate_user(&self, username: &str, password: &str) -> Result { + match self.store.get_user_by_username(username).await { + Some(user) => { + if user.password_hash == format!("hashed_{}", password) { + Ok(user) + } else { + Err(ApiError::Unauthorized) + } + } + None => Err(ApiError::NotFound("用户不存在".to_string())), + } + } +} \ No newline at end of file diff --git a/src/storage/memory.rs b/src/storage/memory.rs new file mode 100644 index 0000000..ee8fcf8 --- /dev/null +++ b/src/storage/memory.rs @@ -0,0 +1,68 @@ +//! 内存存储实现 + +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; +use uuid::Uuid; +use crate::models::user::User; + +/// 线程安全的用户存储类型 +pub type UserStorage = Arc>>; + +/// 内存用户存储 +#[derive(Clone)] +pub struct MemoryUserStore { + users: UserStorage, +} + +impl MemoryUserStore { + /// 创建新的内存存储实例 + pub fn new() -> Self { + Self { + users: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// 创建用户 + pub async fn create_user(&self, user: User) -> Result { + let mut users = self.users.write().unwrap(); + users.insert(user.id, user.clone()); + Ok(user) + } + + /// 根据 ID 获取用户 + pub async fn get_user(&self, id: &Uuid) -> Option { + let users = self.users.read().unwrap(); + users.get(id).cloned() + } + + /// 根据用户名获取用户 + pub async fn get_user_by_username(&self, username: &str) -> Option { + let users = self.users.read().unwrap(); + users.values().find(|u| u.username == username).cloned() + } + + /// 获取所有用户 + pub async fn list_users(&self) -> Vec { + let users = self.users.read().unwrap(); + users.values().cloned().collect() + } + + /// 更新用户 + pub async fn update_user(&self, id: &Uuid, updated_user: User) -> Option { + let mut users = self.users.write().unwrap(); + users.insert(*id, updated_user.clone()); + Some(updated_user) + } + + /// 删除用户 + pub async fn delete_user(&self, id: &Uuid) -> bool { + let mut users = self.users.write().unwrap(); + users.remove(id).is_some() + } +} + +impl Default for MemoryUserStore { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000..129f673 --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,5 @@ +//! 数据存储模块 + +pub mod memory; + +pub use memory::MemoryUserStore; \ No newline at end of file diff --git a/src/utils/errors.rs b/src/utils/errors.rs new file mode 100644 index 0000000..6cd4f43 --- /dev/null +++ b/src/utils/errors.rs @@ -0,0 +1,44 @@ +//! 错误处理模块 + +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + Json, +}; +use serde_json::json; + +/// API 错误类型 +#[derive(Debug)] +pub enum ApiError { + ValidationError(String), + NotFound(String), + InternalError(String), + Unauthorized, + Conflict(String), +} + +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + let (status, error_message) = match self { + ApiError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg), + ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg), + ApiError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), + ApiError::Unauthorized => (StatusCode::UNAUTHORIZED, "未授权".to_string()), + ApiError::Conflict(msg) => (StatusCode::CONFLICT, msg), + }; + + let body = Json(json!({ + "error": error_message, + "status": status.as_u16() + })); + + (status, body).into_response() + } +} + +/// 从验证错误转换为 API 错误 +impl From for ApiError { + fn from(errors: validator::ValidationErrors) -> Self { + ApiError::ValidationError(format!("验证失败: {:?}", errors)) + } +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..4ce9282 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,5 @@ +//! 工具函数模块 + +pub mod errors; + +pub use errors::ApiError; \ No newline at end of file