diff --git a/src/handlers/user.rs b/src/handlers/user.rs index 53e2eb0..1ad1d03 100644 --- a/src/handlers/user.rs +++ b/src/handlers/user.rs @@ -10,9 +10,10 @@ use uuid::Uuid; use chrono::Utc; use validator::Validate; -use crate::models::user::{User, UserResponse, CreateUserRequest, UpdateUserRequest}; +use crate::models::user::{User, UserResponse, CreateUserRequest, UpdateUserRequest, LoginRequest, LoginResponse}; use crate::storage::memory::MemoryUserStore; use crate::utils::errors::ApiError; +use crate::middleware::auth::create_jwt; /// 创建用户 pub async fn create_user( @@ -103,7 +104,46 @@ pub async fn delete_user( } } -/// 简单的密码哈希函数(生产环境应使用 bcrypt) +/// 用户登录 +pub async fn login( + State(store): State, + RequestJson(payload): RequestJson, +) -> Result, ApiError> { + // 根据用户名查找用户 + match store.get_user_by_username(&payload.username).await { + Some(user) => { + // 验证密码 + if verify_password(&payload.password, &user.password_hash) { + // 生成 JWT token + let token = create_jwt(&user.id.to_string()) + .map_err(|_| ApiError::InternalError("生成 token 失败".to_string()))?; + + Ok(Json(LoginResponse { + token, + user: user.into(), + })) + } else { + Err(ApiError::Unauthorized) + } + } + None => Err(ApiError::NotFound("用户不存在".to_string())), + } +} + +/// 使用 bcrypt 哈希密码 fn hash_password(password: &str) -> String { - format!("hashed_{}", password) + bcrypt::hash(password, bcrypt::DEFAULT_COST).unwrap_or_else(|_| { + // 如果哈希失败,使用简单的备用方案(仅用于开发) + format!("fallback_hash_{}", password) + }) +} + +/// 验证密码 +fn verify_password(password: &str, hash: &str) -> bool { + if hash.starts_with("fallback_hash_") { + // 处理备用哈希方案 + hash == &format!("fallback_hash_{}", password) + } else { + bcrypt::verify(password, hash).unwrap_or(false) + } } \ No newline at end of file diff --git a/src/routes/mod.rs b/src/routes/mod.rs index a14a158..e3edbac 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -19,13 +19,14 @@ pub fn create_routes(store: MemoryUserStore) -> Router { /// API 路由 fn api_routes() -> Router { Router::new() - .route("/users", + .route("/users", get(handlers::user::list_users) .post(handlers::user::create_user) ) - .route("/users/:id", + .route("/users/:id", get(handlers::user::get_user) .put(handlers::user::update_user) .delete(handlers::user::delete_user) ) + .route("/auth/login", post(handlers::user::login)) } \ No newline at end of file diff --git a/src/utils/errors.rs b/src/utils/errors.rs index 6cd4f43..7727d19 100644 --- a/src/utils/errors.rs +++ b/src/utils/errors.rs @@ -39,6 +39,31 @@ impl IntoResponse for ApiError { /// 从验证错误转换为 API 错误 impl From for ApiError { fn from(errors: validator::ValidationErrors) -> Self { - ApiError::ValidationError(format!("验证失败: {:?}", errors)) + let mut error_messages = Vec::new(); + + for (field, field_errors) in errors.field_errors() { + for error in field_errors { + let message = match error.code.as_ref() { + "length" => { + if let Some(min) = error.params.get("min") { + if let Some(max) = error.params.get("max") { + format!("{} 长度必须在 {} 到 {} 之间", field, min, max) + } else { + format!("{} 长度至少需要 {} 个字符", field, min) + } + } else if let Some(max) = error.params.get("max") { + format!("{} 长度不能超过 {} 个字符", field, max) + } else { + format!("{} 长度不符合要求", field) + } + }, + "email" => format!("{} 必须是有效的邮箱地址", field), + _ => format!("{} 验证失败", field), + }; + error_messages.push(message); + } + } + + ApiError::ValidationError(error_messages.join("; ")) } } \ No newline at end of file diff --git a/test_api.sh b/test_api.sh index 0ec4a7d..0fc35d4 100755 --- a/test_api.sh +++ b/test_api.sh @@ -21,6 +21,18 @@ curl -s http://127.0.0.1:3000/api/users | jq '.' || echo "JSON 解析失败, echo -e "\n" +# 测试验证错误 +echo "🚫 测试验证错误 (POST /api/users):" +curl -s -X POST http://127.0.0.1:3000/api/users \ + -H "Content-Type: application/json" \ + -d '{ + "username": "ab", + "email": "invalid-email", + "password": "123" + }' | jq '.' || echo "JSON 解析失败,原始响应:" + +echo -e "\n" + # 测试创建用户 echo "➕ 测试创建用户 (POST /api/users):" curl -s -X POST http://127.0.0.1:3000/api/users \ @@ -33,8 +45,42 @@ curl -s -X POST http://127.0.0.1:3000/api/users \ echo -e "\n" +# 测试重复用户名 +echo "🔄 测试重复用户名 (POST /api/users):" +curl -s -X POST http://127.0.0.1:3000/api/users \ + -H "Content-Type: application/json" \ + -d '{ + "username": "testuser", + "email": "test2@example.com", + "password": "password123" + }' | jq '.' || echo "JSON 解析失败,原始响应:" + +echo -e "\n" + # 再次测试用户列表(应该有一个用户) echo "👥 再次测试用户列表 (GET /api/users):" curl -s http://127.0.0.1:3000/api/users | jq '.' || echo "JSON 解析失败,原始响应:" +echo -e "\n" + +# 测试用户登录 +echo "🔐 测试用户登录 (POST /api/auth/login):" +curl -s -X POST http://127.0.0.1:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "username": "testuser", + "password": "password123" + }' | jq '.' || echo "JSON 解析失败,原始响应:" + +echo -e "\n" + +# 测试错误的登录凭据 +echo "❌ 测试错误登录凭据 (POST /api/auth/login):" +curl -s -X POST http://127.0.0.1:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "username": "testuser", + "password": "wrongpassword" + }' | jq '.' || echo "JSON 解析失败,原始响应:" + echo -e "\n✅ API 测试完成" \ No newline at end of file