From f82dd8d5de915758f023433754c1716accec4c1a Mon Sep 17 00:00:00 2001 From: milky0217 Date: Mon, 25 May 2026 13:32:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0PAYMENT=5FMAINTENANCE?= =?UTF-8?q?=5FMODE=E7=BB=B4=E6=8A=A4=E6=A8=A1=E5=BC=8F=20+=20ServiceUnavai?= =?UTF-8?q?lable=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db.rs | 14 +++++++++----- src/error.rs | 4 ++++ src/handlers/payment.rs | 30 ++++++++++++++++++++++++++++-- src/handlers/user.rs | 9 +++++++-- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/db.rs b/src/db.rs index f93e776..91dc8d6 100644 --- a/src/db.rs +++ b/src/db.rs @@ -9,11 +9,14 @@ use crate::error::AppError; // 用于插入weather_data的数据 pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result { - // 配额检查:非付费用户数据条数限制 - let user = get_user_by_id(pool, user_id).await?; - let is_paid_active = user.is_paid && user.paid_expires_at.map_or(true, |expires| expires > Utc::now()); - - if !is_paid_active { + // 维护模式:跳过所有配额检查 + let is_maintenance = std::env::var("PAYMENT_MAINTENANCE_MODE").ok() == Some("true".to_string()); + if !is_maintenance { + // 配额检查:非付费用户数据条数限制 + let user = get_user_by_id(pool, user_id).await?; + let is_paid_active = user.is_paid && user.paid_expires_at.map_or(true, |expires| expires > Utc::now()); + + if !is_paid_active { let current_count = count_user_weather_data(pool, user_id).await?; let limit: i64 = env::var("FREE_USER_DATA_LIMIT") .unwrap_or_else(|_| "20".to_string()) @@ -24,6 +27,7 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user return Err(AppError::Forbidden("数据条数已达上限,请升级为付费用户".to_string())); } } + } // 准备插入数据的 SQL 语句 let insert_query = r#" diff --git a/src/error.rs b/src/error.rs index 954188c..1f1ef01 100644 --- a/src/error.rs +++ b/src/error.rs @@ -53,6 +53,7 @@ pub enum AppError { Internal(String), Database(String), TooManyRequests(String), + ServiceUnavailable(String), } impl fmt::Display for AppError { @@ -65,6 +66,7 @@ impl fmt::Display for AppError { AppError::Internal(msg) => write!(f, "{}", msg), AppError::Database(msg) => write!(f, "{}", msg), AppError::TooManyRequests(msg) => write!(f, "{}", msg), + AppError::ServiceUnavailable(msg) => write!(f, "{}", msg), } } } @@ -78,6 +80,7 @@ impl ResponseError for AppError { AppError::BadRequest(_) => StatusCode::BAD_REQUEST, AppError::Internal(_) | AppError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, AppError::TooManyRequests(_) => StatusCode::TOO_MANY_REQUESTS, + AppError::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE, } } @@ -169,6 +172,7 @@ impl ResponseError for AppError { AppError::BadRequest(_) => "请求无效", AppError::Internal(_) | AppError::Database(_) => "服务器错误", AppError::TooManyRequests(_) => "请求过于频繁", + AppError::ServiceUnavailable(_) => "服务暂不可用", } ); diff --git a/src/handlers/payment.rs b/src/handlers/payment.rs index 0d2bafc..33d779f 100644 --- a/src/handlers/payment.rs +++ b/src/handlers/payment.rs @@ -255,6 +255,16 @@ fn build_alipay_form_html( // ===== 提取 JWT token ===== +/// 维护模式守卫:PAYMENT_MAINTENANCE_MODE=true 时所有支付接口返回 503 +fn check_payment_maintenance() -> Result<(), AppError> { + if std::env::var("PAYMENT_MAINTENANCE_MODE").ok() == Some("true".to_string()) { + return Err(AppError::ServiceUnavailable( + "支付系统维护中,请稍后再试".to_string() + )); + } + Ok(()) +} + fn extract_token(req: &HttpRequest) -> Option { req.headers() .get("Authorization")? @@ -272,6 +282,7 @@ fn get_jwt_secret() -> String { #[get("/payment")] pub async fn payment_index() -> Result { + check_payment_maintenance()?; let html = r##" @@ -594,6 +605,7 @@ pub async fn payment_page( pool: web::Data, query: web::Query, ) -> Result { + check_payment_maintenance()?; let token = extract_token(&req) .or_else(|| query.jwt.clone()) .ok_or_else(|| AppError::Unauthorized("未登录".to_string()))?; @@ -665,6 +677,7 @@ pub async fn alipay_pay_page( req: HttpRequest, query: web::Query, ) -> Result { + check_payment_maintenance()?; let token = extract_token(&req).ok_or_else(|| AppError::Unauthorized("未登录".to_string()))?; crate::auth::verify_token(&token, &get_jwt_secret()) .map_err(|_| AppError::Unauthorized("Token 无效".to_string()))?; @@ -908,6 +921,7 @@ pub async fn create_order( claims: web::ReqData, body: web::Json, ) -> Result { + check_payment_maintenance()?; let user_id = claims.user_id; let pkg = match get_package_info(&body.package_type) { @@ -950,6 +964,7 @@ pub async fn mock_confirm( claims: web::ReqData, body: web::Json, ) -> Result { + check_payment_maintenance()?; let user_id = claims.user_id; let expires_at = @@ -973,6 +988,7 @@ pub async fn sync_order( claims: web::ReqData, body: web::Json, ) -> Result { + check_payment_maintenance()?; let user_id = claims.user_id; // 尝试确认订单(幂等),失败时记录日志 @@ -1015,6 +1031,7 @@ pub async fn cancel_order( claims: web::ReqData, body: web::Json, ) -> Result { + check_payment_maintenance()?; db::cancel_payment_order(pool.get_ref(), &body.order_id, claims.user_id).await?; Ok(HttpResponse::Ok().json(serde_json::json!({ "success": true, @@ -1052,6 +1069,8 @@ pub async fn get_user_quota( ) -> Result { let user_id = claims.user_id; + let is_maintenance = std::env::var("PAYMENT_MAINTENANCE_MODE").ok() == Some("true".to_string()); + let (used, is_paid_active, paid_expires_at) = db::get_user_quota(pool.get_ref(), user_id).await?; let limit: i64 = std::env::var("FREE_USER_DATA_LIMIT") @@ -1059,14 +1078,21 @@ pub async fn get_user_quota( .and_then(|v| v.parse().ok()) .unwrap_or(20); + let (unlimited, active) = if is_maintenance { + (true, true) + } else { + (is_paid_active, is_paid_active) + }; + Ok(HttpResponse::Ok().json(serde_json::json!({ "success": true, "data": { "used": used, "limit": limit, - "unlimited": is_paid_active, - "is_paid_active": is_paid_active, + "unlimited": unlimited, + "is_paid_active": active, "paid_expires_at": paid_expires_at, + "maintenance_mode": is_maintenance, } }))) } diff --git a/src/handlers/user.rs b/src/handlers/user.rs index d20930e..bba1f0d 100644 --- a/src/handlers/user.rs +++ b/src/handlers/user.rs @@ -16,8 +16,13 @@ pub async fn get_current_user_profile( info!("获取当前用户信息, 用户ID: {}", user_id); let user = db::get_user_by_id(pool.get_ref(), user_id).await?; - let is_paid_active = user.is_paid && - user.paid_expires_at.map_or(true, |expires| expires > chrono::Utc::now()); + let is_maintenance = std::env::var("PAYMENT_MAINTENANCE_MODE").ok() == Some("true".to_string()); + let is_paid_active = if is_maintenance { + true + } else { + user.is_paid && + user.paid_expires_at.map_or(true, |expires| expires > chrono::Utc::now()) + }; Ok(HttpResponse::Ok().json(serde_json::json!({ "success": true,