diff --git a/.env b/.env index de90950..9a1b1db 100644 --- a/.env +++ b/.env @@ -6,4 +6,5 @@ JWT_SECRET="your_super_secret_key" SSL_KEY_PATH=/etc/ssl/private/private.key SSL_CERT_PATH=/etc/ssl/certs/full_chain.pem RUST_LOG=info -APP_VERSION="0.2.0" \ No newline at end of file +APP_VERSION="0.2.0" +FREE_USER_DATA_LIMIT=20 \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d376a14..c6d14ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use actix_web::middleware::from_fn; -use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, web}; +use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, put, web}; use include_dir::{Dir, include_dir}; use log::{debug, error, info, warn}; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; @@ -14,7 +14,7 @@ use auth::{generate_token, jwt_middleware}; use db::{create_pool, insert_weather_data}; use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData}; -use crate::models::{Claims, TokenResponse}; +use crate::models::{Claims, TokenResponse, UpdatePaymentRequest}; // 编译时嵌入整个 static 目录 static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static"); @@ -214,11 +214,14 @@ async fn login( async fn post_weather_data( data: web::Json, pool: web::Data, + claims: web::ReqData, ) -> impl Responder { info!("Received weather data, preparing to insert into DB..."); + let user_id = claims.user_id; + // 调用 db.rs 中的函数来处理数据库逻辑 - match insert_weather_data(pool.get_ref(), &data).await { + match insert_weather_data(pool.get_ref(), &data, user_id).await { Ok(inserted_id) => { info!( "Successfully inserted weather data with id: {}", @@ -233,14 +236,9 @@ async fn post_weather_data( } Err(error_msg) => { error!("Database operation failed: {}", error_msg); - // 根据错误信息返回统一格式的错误响应 - let mut errcode = 500; - if error_msg.starts_with("未找到openid") { - errcode = 404; - } HttpResponse::Ok().json(serde_json::json!({ "success": false, - "errcode": errcode, + "errcode": 500, "errmsg": error_msg })) } @@ -547,6 +545,119 @@ async fn delete_weather( } } +// 管理员获取用户信息 +#[get("/api/admin/users/{id}")] +async fn admin_get_user( + path: web::Path, + pool: web::Data, + claims: web::ReqData, +) -> impl Responder { + let target_user_id = path.into_inner(); + info!("管理员获取用户信息, 目标用户ID: {}", target_user_id); + + // 验证当前用户是否为管理员 + let current_user = match db::get_user_by_id(pool.get_ref(), claims.user_id).await { + Ok(user) => user, + Err(e) => { + error!("获取当前用户信息失败: {}", e); + return HttpResponse::Ok().json(serde_json::json!({ + "success": false, + "errcode": 500, + "errmsg": "获取用户信息失败" + })); + } + }; + + if !current_user.is_admin { + return HttpResponse::Forbidden().json(serde_json::json!({ + "success": false, + "errcode": 403, + "errmsg": "无权限执行此操作" + })); + } + + // 获取目标用户信息 + match db::get_user_by_id(pool.get_ref(), target_user_id).await { + Ok(user) => HttpResponse::Ok().json(serde_json::json!({ + "success": true, + "data": user + })), + Err(e) => { + error!("获取目标用户信息失败: {}", e); + let errcode = if e.starts_with("未找到") { 404 } else { 500 }; + HttpResponse::Ok().json(serde_json::json!({ + "success": false, + "errcode": errcode, + "errmsg": e + })) + } + } +} + +// 管理员更新用户付费状态 +#[put("/api/admin/users/{id}/payment")] +async fn admin_update_user_payment( + path: web::Path, + pool: web::Data, + claims: web::ReqData, + body: web::Json, +) -> impl Responder { + let target_user_id = path.into_inner(); + info!("管理员更新用户付费状态, 目标用户ID: {}", target_user_id); + + // 验证当前用户是否为管理员 + let current_user = match db::get_user_by_id(pool.get_ref(), claims.user_id).await { + Ok(user) => user, + Err(e) => { + error!("获取当前用户信息失败: {}", e); + return HttpResponse::Ok().json(serde_json::json!({ + "success": false, + "errcode": 500, + "errmsg": "获取用户信息失败" + })); + } + }; + + if !current_user.is_admin { + return HttpResponse::Forbidden().json(serde_json::json!({ + "success": false, + "errcode": 403, + "errmsg": "无权限执行此操作" + })); + } + + // 解析 paid_expires_at + let paid_expires_at = match &body.paid_expires_at { + Some(date_str) => match chrono::DateTime::parse_from_rfc3339(date_str) { + Ok(dt) => Some(dt.with_timezone(&chrono::Utc)), + Err(e) => { + return HttpResponse::BadRequest().json(serde_json::json!({ + "success": false, + "errcode": 400, + "errmsg": format!("日期格式错误: {}", e) + })); + } + }, + None => None, + }; + + // 更新用户付费状态 + match db::update_user_payment_status(pool.get_ref(), target_user_id, body.is_paid, paid_expires_at).await { + Ok(_) => HttpResponse::Ok().json(serde_json::json!({ + "success": true, + "message": "用户付费状态已更新" + })), + Err(e) => { + error!("更新用户付费状态失败: {}", e); + HttpResponse::Ok().json(serde_json::json!({ + "success": false, + "errcode": 500, + "errmsg": e + })) + } + } +} + // 在 main.rs 中添加以下函数,用于处理静态文件请求 async fn serve_static_files(path: web::Path) -> impl Responder { // 获取请求的文件路径(例如 "css/style.css") @@ -615,7 +726,9 @@ fn create_server_config( .service(post_weather_data) .service(get_weather_brief) // 新增 .service(generate_temp_token_handler) - .service(delete_weather), + .service(delete_weather) + .service(admin_get_user) + .service(admin_update_user_payment), ) }