feat: 添加PAYMENT_MAINTENANCE_MODE维护模式 + ServiceUnavailable错误

This commit is contained in:
2026-05-25 13:32:16 +08:00
parent 72cbb8ff28
commit f82dd8d5de
4 changed files with 48 additions and 9 deletions

View File

@@ -9,6 +9,9 @@ use crate::error::AppError;
// 用于插入weather_data的数据
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, AppError> {
// 维护模式:跳过所有配额检查
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());
@@ -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#"

View File

@@ -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(_) => "服务暂不可用",
}
);

View File

@@ -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<String> {
req.headers()
.get("Authorization")?
@@ -272,6 +282,7 @@ fn get_jwt_secret() -> String {
#[get("/payment")]
pub async fn payment_index() -> Result<HttpResponse, AppError> {
check_payment_maintenance()?;
let html = r##"<!DOCTYPE html>
<html lang="zh-CN">
<head>
@@ -594,6 +605,7 @@ pub async fn payment_page(
pool: web::Data<PgPool>,
query: web::Query<PaymentPageQuery>,
) -> Result<HttpResponse, AppError> {
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<AlipayPayQuery>,
) -> Result<HttpResponse, AppError> {
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<Claims>,
body: web::Json<CreateOrderRequest>,
) -> Result<HttpResponse, AppError> {
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<Claims>,
body: web::Json<MockConfirmRequest>,
) -> Result<HttpResponse, AppError> {
check_payment_maintenance()?;
let user_id = claims.user_id;
let expires_at =
@@ -973,6 +988,7 @@ pub async fn sync_order(
claims: web::ReqData<Claims>,
body: web::Json<MockConfirmRequest>,
) -> Result<HttpResponse, AppError> {
check_payment_maintenance()?;
let user_id = claims.user_id;
// 尝试确认订单(幂等),失败时记录日志
@@ -1015,6 +1031,7 @@ pub async fn cancel_order(
claims: web::ReqData<Claims>,
body: web::Json<CancelOrderRequest>,
) -> Result<HttpResponse, AppError> {
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<HttpResponse, AppError> {
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,
}
})))
}

View File

@@ -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,