feat: [阶段1] 项目初始化和基础设置
- 创建 Cargo.toml 配置文件,包含所有必要依赖 - 建立完整的项目模块结构(config, models, handlers, routes, services, storage, middleware, utils) - 实现用户数据模型和内存存储 - 创建基础的 HTTP 处理器和路由配置 - 添加错误处理和 JWT 认证中间件 - 配置环境变量和日志系统 - 创建项目文档和学习指南 - 服务器可以成功编译和启动
This commit is contained in:
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