feat: [阶段1] 项目初始化和基础设置
- 创建 Cargo.toml 配置文件,包含所有必要依赖 - 建立完整的项目模块结构(config, models, handlers, routes, services, storage, middleware, utils) - 实现用户数据模型和内存存储 - 创建基础的 HTTP 处理器和路由配置 - 添加错误处理和 JWT 认证中间件 - 配置环境变量和日志系统 - 创建项目文档和学习指南 - 服务器可以成功编译和启动
This commit is contained in:
9
.env.example
Normal file
9
.env.example
Normal file
@@ -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
|
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Rust
|
||||||
|
/target/
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
.env
|
||||||
|
|
||||||
|
# 日志文件
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# 数据库文件
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# 临时文件
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*~
|
44
Cargo.toml
Normal file
44
Cargo.toml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[package]
|
||||||
|
name = "rust-user-api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Your Name <your.email@example.com>"]
|
||||||
|
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"
|
138
README.md
Normal file
138
README.md
Normal file
@@ -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 <repository-url>
|
||||||
|
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
|
121
git_learning_guide.md
Normal file
121
git_learning_guide.md
Normal file
@@ -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. **学习最佳实践**: 培养良好的版本控制习惯
|
||||||
|
|
||||||
|
这种方法不仅有助于当前的学习,也为将来的项目开发建立了良好的基础。
|
719
implementation_details.md
Normal file
719
implementation_details.md
Normal file
@@ -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<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"message": "Welcome to Rust User API",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn health_check() -> Json<Value> {
|
||||||
|
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<Utc>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct UserResponse {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct CreateUserRequest {
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct UpdateUserRequest {
|
||||||
|
pub username: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<User> 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<RwLock<HashMap<Uuid, User>>>;
|
||||||
|
|
||||||
|
#[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<User, String> {
|
||||||
|
let mut users = self.users.write().unwrap();
|
||||||
|
users.insert(user.id, user.clone());
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user(&self, id: &Uuid) -> Option<User> {
|
||||||
|
let users = self.users.read().unwrap();
|
||||||
|
users.get(id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_users(&self) -> Vec<User> {
|
||||||
|
let users = self.users.read().unwrap();
|
||||||
|
users.values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_user(&self, id: &Uuid, updated_user: User) -> Option<User> {
|
||||||
|
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<RwLock<T>>`)
|
||||||
|
- 异步方法定义
|
||||||
|
- 错误处理基础
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 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<MemoryUserStore>,
|
||||||
|
RequestJson(payload): RequestJson<CreateUserRequest>,
|
||||||
|
) -> Result<(StatusCode, Json<UserResponse>), 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<MemoryUserStore>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<Json<UserResponse>, 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<MemoryUserStore>,
|
||||||
|
) -> Json<Vec<UserResponse>> {
|
||||||
|
let users = store.list_users().await;
|
||||||
|
let responses: Vec<UserResponse> = users.into_iter().map(|u| u.into()).collect();
|
||||||
|
Json(responses)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_user(
|
||||||
|
State(store): State<MemoryUserStore>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
RequestJson(payload): RequestJson<UpdateUserRequest>,
|
||||||
|
) -> Result<Json<UserResponse>, 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<MemoryUserStore>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> 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<MemoryUserStore> {
|
||||||
|
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<MemoryUserStore>,
|
||||||
|
RequestJson(payload): RequestJson<CreateUserRequest>,
|
||||||
|
) -> Result<(StatusCode, Json<UserResponse>), 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<Response, StatusCode> {
|
||||||
|
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<Claims, jsonwebtoken::errors::Error> {
|
||||||
|
let key = DecodingKey::from_secret("secret".as_ref());
|
||||||
|
let validation = Validation::default();
|
||||||
|
|
||||||
|
decode::<Claims>(token, &key, &validation)
|
||||||
|
.map(|data| data.claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_jwt(user_id: &str) -> Result<String, jsonwebtoken::errors::Error> {
|
||||||
|
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<MemoryUserStore>,
|
||||||
|
RequestJson(payload): RequestJson<LoginRequest>,
|
||||||
|
) -> Result<Json<LoginResponse>, 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 <jwt_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 开发的各个方面。
|
381
learning_guide.md
Normal file
381
learning_guide.md
Normal file
@@ -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<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"status": "ok",
|
||||||
|
"timestamp": Utc::now(),
|
||||||
|
"uptime": "1h 30m"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 练习2: 添加版本信息端点
|
||||||
|
async fn version() -> Json<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"version": env!("CARGO_PKG_VERSION"),
|
||||||
|
"name": env!("CARGO_PKG_NAME")
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 常见问题和解决方案
|
||||||
|
- **编译错误**: 检查依赖版本兼容性
|
||||||
|
- **异步函数理解**: 从同步思维转向异步思维
|
||||||
|
- **模块导入**: 理解 Rust 的模块系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 第二周:数据模型和存储(阶段 3-4)
|
||||||
|
|
||||||
|
#### 学习目标
|
||||||
|
- 掌握 Serde 序列化
|
||||||
|
- 理解内存存储设计
|
||||||
|
- 实现完整的 CRUD 操作
|
||||||
|
|
||||||
|
#### 关键概念
|
||||||
|
1. **数据建模**
|
||||||
|
- 结构体设计原则
|
||||||
|
- 序列化和反序列化
|
||||||
|
- 类型转换和 From trait
|
||||||
|
|
||||||
|
2. **并发安全**
|
||||||
|
- `Arc<RwLock<T>>` 模式
|
||||||
|
- 读写锁的使用场景
|
||||||
|
- 线程安全的数据共享
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 练习2: 实现用户搜索
|
||||||
|
pub async fn search_users(
|
||||||
|
State(store): State<MemoryUserStore>,
|
||||||
|
Query(params): Query<SearchParams>,
|
||||||
|
) -> Json<Vec<UserResponse>> {
|
||||||
|
// 实现按用户名或邮箱搜索
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 调试技巧
|
||||||
|
- 使用 `println!` 和 `dbg!` 宏调试
|
||||||
|
- 理解借用检查器错误信息
|
||||||
|
- 使用 Postman 测试 API 端点
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 第三周:错误处理和验证(阶段 5)
|
||||||
|
|
||||||
|
#### 学习目标
|
||||||
|
- 掌握 Rust 错误处理模式
|
||||||
|
- 实现请求数据验证
|
||||||
|
- 创建统一的错误响应
|
||||||
|
|
||||||
|
#### 关键概念
|
||||||
|
1. **错误处理哲学**
|
||||||
|
- `Result<T, E>` 类型
|
||||||
|
- `?` 操作符的使用
|
||||||
|
- 错误传播和转换
|
||||||
|
|
||||||
|
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<Response, StatusCode> {
|
||||||
|
if claims.role != UserRole::Admin {
|
||||||
|
return Err(StatusCode::FORBIDDEN);
|
||||||
|
}
|
||||||
|
Ok(next.run(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 练习2: 实现刷新 Token 机制
|
||||||
|
pub async fn refresh_token(
|
||||||
|
State(store): State<MemoryUserStore>,
|
||||||
|
RequestJson(payload): RequestJson<RefreshTokenRequest>,
|
||||||
|
) -> Result<Json<TokenResponse>, 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 开发的各个方面,从基础概念到生产级应用开发。
|
167
project_plan.md
Normal file
167
project_plan.md
Normal file
@@ -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<Utc>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UserResponse {
|
||||||
|
pub id: u64,
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 学习路径
|
||||||
|
|
||||||
|
### 阶段 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 应用的开发者。
|
45
src/config/mod.rs
Normal file
45
src/config/mod.rs
Normal file
@@ -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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/handlers/mod.rs
Normal file
22
src/handlers/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//! HTTP 请求处理器模块
|
||||||
|
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
use axum::{response::Json, http::StatusCode};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
/// 根路径处理器
|
||||||
|
pub async fn root() -> Json<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"message": "欢迎使用 Rust User API",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 健康检查处理器
|
||||||
|
pub async fn health_check() -> (StatusCode, Json<Value>) {
|
||||||
|
(StatusCode::OK, Json(json!({
|
||||||
|
"status": "healthy",
|
||||||
|
"timestamp": chrono::Utc::now()
|
||||||
|
})))
|
||||||
|
}
|
109
src/handlers/user.rs
Normal file
109
src/handlers/user.rs
Normal file
@@ -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<MemoryUserStore>,
|
||||||
|
RequestJson(payload): RequestJson<CreateUserRequest>,
|
||||||
|
) -> Result<(StatusCode, Json<UserResponse>), 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<MemoryUserStore>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<Json<UserResponse>, 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<MemoryUserStore>,
|
||||||
|
) -> Json<Vec<UserResponse>> {
|
||||||
|
let users = store.list_users().await;
|
||||||
|
let responses: Vec<UserResponse> = users.into_iter().map(|u| u.into()).collect();
|
||||||
|
Json(responses)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新用户
|
||||||
|
pub async fn update_user(
|
||||||
|
State(store): State<MemoryUserStore>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
RequestJson(payload): RequestJson<UpdateUserRequest>,
|
||||||
|
) -> Result<Json<UserResponse>, 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<MemoryUserStore>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<StatusCode, ApiError> {
|
||||||
|
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)
|
||||||
|
}
|
18
src/lib.rs
Normal file
18
src/lib.rs
Normal file
@@ -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;
|
35
src/main.rs
Normal file
35
src/main.rs
Normal file
@@ -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();
|
||||||
|
}
|
70
src/middleware/auth.rs
Normal file
70
src/middleware/auth.rs
Normal file
@@ -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<Response, StatusCode> {
|
||||||
|
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<Claims, jsonwebtoken::errors::Error> {
|
||||||
|
let key = DecodingKey::from_secret("your-secret-key".as_ref());
|
||||||
|
let validation = Validation::default();
|
||||||
|
|
||||||
|
decode::<Claims>(token, &key, &validation)
|
||||||
|
.map(|data| data.claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建 JWT token
|
||||||
|
pub fn create_jwt(user_id: &str) -> Result<String, jsonwebtoken::errors::Error> {
|
||||||
|
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)
|
||||||
|
}
|
5
src/middleware/mod.rs
Normal file
5
src/middleware/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//! 中间件模块
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
|
|
||||||
|
pub use auth::{auth_middleware, Claims};
|
5
src/models/mod.rs
Normal file
5
src/models/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//! 数据模型模块
|
||||||
|
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
pub use user::{User, UserResponse, CreateUserRequest, UpdateUserRequest, LoginRequest, LoginResponse};
|
72
src/models/user.rs
Normal file
72
src/models/user.rs
Normal file
@@ -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<Utc>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 用户响应(不包含敏感信息)
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct UserResponse {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建用户请求
|
||||||
|
#[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<String>,
|
||||||
|
#[validate(email)]
|
||||||
|
pub email: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登录请求
|
||||||
|
#[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<User> for UserResponse {
|
||||||
|
fn from(user: User) -> Self {
|
||||||
|
UserResponse {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
created_at: user.created_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/routes/mod.rs
Normal file
31
src/routes/mod.rs
Normal file
@@ -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<MemoryUserStore> {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
5
src/services/mod.rs
Normal file
5
src/services/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//! 业务逻辑服务模块
|
||||||
|
|
||||||
|
pub mod user_service;
|
||||||
|
|
||||||
|
pub use user_service::UserService;
|
30
src/services/user_service.rs
Normal file
30
src/services/user_service.rs
Normal file
@@ -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<User, ApiError> {
|
||||||
|
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())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
src/storage/memory.rs
Normal file
68
src/storage/memory.rs
Normal file
@@ -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<RwLock<HashMap<Uuid, User>>>;
|
||||||
|
|
||||||
|
/// 内存用户存储
|
||||||
|
#[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<User, String> {
|
||||||
|
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<User> {
|
||||||
|
let users = self.users.read().unwrap();
|
||||||
|
users.get(id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据用户名获取用户
|
||||||
|
pub async fn get_user_by_username(&self, username: &str) -> Option<User> {
|
||||||
|
let users = self.users.read().unwrap();
|
||||||
|
users.values().find(|u| u.username == username).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取所有用户
|
||||||
|
pub async fn list_users(&self) -> Vec<User> {
|
||||||
|
let users = self.users.read().unwrap();
|
||||||
|
users.values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新用户
|
||||||
|
pub async fn update_user(&self, id: &Uuid, updated_user: User) -> Option<User> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
5
src/storage/mod.rs
Normal file
5
src/storage/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//! 数据存储模块
|
||||||
|
|
||||||
|
pub mod memory;
|
||||||
|
|
||||||
|
pub use memory::MemoryUserStore;
|
44
src/utils/errors.rs
Normal file
44
src/utils/errors.rs
Normal file
@@ -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<validator::ValidationErrors> for ApiError {
|
||||||
|
fn from(errors: validator::ValidationErrors) -> Self {
|
||||||
|
ApiError::ValidationError(format!("验证失败: {:?}", errors))
|
||||||
|
}
|
||||||
|
}
|
5
src/utils/mod.rs
Normal file
5
src/utils/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//! 工具函数模块
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
|
||||||
|
pub use errors::ApiError;
|
Reference in New Issue
Block a user