优化: Keep-Alive/rate-limiter/dead-code清理 + deploy.sh蓝绿自动检测 + 压测文档

This commit is contained in:
2026-07-03 19:45:52 +08:00
parent 6d066c8e82
commit 178008496e
18 changed files with 645 additions and 57 deletions

View File

@@ -149,7 +149,31 @@ pub async fn refresh_token(
pool: web::Data<PgPool>,
req: web::Json<RefreshTokenRequest>,
app_state: web::Data<AppState>,
http_req: HttpRequest,
) -> impl Responder {
let client_ip = http_req
.headers()
.get("X-Forwarded-For")
.and_then(|v| v.to_str().ok())
.map(|s| s.split(',').next().unwrap_or(s).trim().to_string())
.or_else(|| {
http_req
.headers()
.get("X-Real-IP")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
})
.unwrap_or_else(|| {
http_req
.peer_addr()
.map(|a| a.ip().to_string())
.unwrap_or_else(|| "unknown".to_string())
});
if let Err(e) = LOGIN_RATE_LIMITER.check_rate_limit(&client_ip).await {
warn!("refresh_token 请求被限流: client_ip={}", client_ip);
return HttpResponse::TooManyRequests().json(ErrorResponse::<()>::error(e.to_string()));
}
let refresh_token = &req.refresh_token;
let refresh_token_record = match db::verify_refresh_token(pool.get_ref(), refresh_token).await {
@@ -247,15 +271,13 @@ pub async fn mock_login(
}
// 如果设置了 MOCK_LOGIN_KEY验证请求头
if let Ok(key) = std::env::var("MOCK_LOGIN_KEY") {
if !key.is_empty() {
let header_key = req.headers()
.get("X-Mock-Key")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if header_key != key {
return HttpResponse::Forbidden().json(ErrorResponse::<()>::error("模拟登录密钥错误"));
}
if let Ok(key) = std::env::var("MOCK_LOGIN_KEY") && !key.is_empty() {
let header_key = req.headers()
.get("X-Mock-Key")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if header_key != key {
return HttpResponse::Forbidden().json(ErrorResponse::<()>::error("模拟登录密钥错误"));
}
}
@@ -613,7 +635,7 @@ pub async fn web_login_confirm(
.await
{
Ok(Some((is_member, expires))) => {
let active = is_member && expires.map_or(true, |e| e > Utc::now());
let active = is_member && expires.is_none_or(|e| e > Utc::now());
(active, expires.map(|e| e.to_rfc3339()))
}
_ => (false, None),
@@ -745,7 +767,7 @@ pub async fn web_login_auto_confirm(
let (is_active_member, membership_expires_at): (bool, Option<String>) = match paid_info {
Some((is_member, expires)) => {
let active = is_member && expires.map_or(true, |e| e > Utc::now());
let active = is_member && expires.is_none_or(|e| e > Utc::now());
(active, expires.map(|e| e.to_rfc3339()))
}
None => (false, None),

View File

@@ -849,8 +849,8 @@ fn urlencoding(s: &str) -> String {
while let Some(b) = chars.next() {
if b == b'%' {
had_escape = true;
let hi = chars.next().and_then(|c| hex_val(c));
let lo = chars.next().and_then(|c| hex_val(c));
let hi = chars.next().and_then(hex_val);
let lo = chars.next().and_then(hex_val);
if let (Some(h), Some(l)) = (hi, lo) {
result.push((h << 4 | l) as char);
} else {
@@ -1171,16 +1171,14 @@ fn check_mock_payment_allowed(req: &HttpRequest) -> Result<(), AppError> {
}
// 规则 3如果设了 MOCK_PAY_KEY验证请求头
if let Ok(key) = std::env::var("MOCK_PAY_KEY") {
if !key.is_empty() {
let header_key = req
.headers()
.get("X-Mock-Key")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if header_key != key {
return Err(AppError::Forbidden("Mock 支付密钥错误".to_string()));
}
if let Ok(key) = std::env::var("MOCK_PAY_KEY") && !key.is_empty() {
let header_key = req
.headers()
.get("X-Mock-Key")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if header_key != key {
return Err(AppError::Forbidden("Mock 支付密钥错误".to_string()));
}
}
@@ -1246,7 +1244,7 @@ pub async fn sync_order(
.ok_or_else(|| AppError::NotFound("用户不存在".to_string()))?;
let (is_member, membership_expires_at) = user;
let is_active_member = is_member && membership_expires_at.map_or(true, |expires| expires > Utc::now());
let is_active_member = is_member && membership_expires_at.is_none_or(|expires| expires > Utc::now());
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
@@ -1407,6 +1405,7 @@ pub async fn payment_login_status(
let code = query.code.trim();
// 查询登录码记录(包含 token 字段用于判断是否已确认)
#[allow(clippy::type_complexity)]
let record: Option<(String, chrono::DateTime<chrono::Utc>, Option<String>, Option<i32>)> =
sqlx::query_as(
"SELECT code, expires_at, token, user_id FROM web_login_codes WHERE code = $1",
@@ -1472,7 +1471,7 @@ pub async fn payment_login_status(
.await
{
Ok(Some((is_member, expires))) => {
let active = is_member && expires.map_or(true, |e| e > Utc::now());
let active = is_member && expires.is_none_or(|e| e > Utc::now());
(active, expires.map(|e| e.to_rfc3339()))
}
_ => (false, None),

View File

@@ -3,8 +3,10 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct SentryEventRequest {
#[allow(dead_code)]
pub level: Option<String>,
pub message: String,
#[allow(dead_code)]
pub stack: Option<String>,
pub page: Option<String>,
pub user_id: Option<i32>,
@@ -28,11 +30,11 @@ pub async fn report_frontend_error(body: web::Json<SentryEventRequest>) -> HttpR
..Default::default()
}));
}
if let Some(extra) = &body.extra {
if let Some(obj) = extra.as_object() {
for (k, v) in obj {
scope.set_extra(k, sentry::protocol::Value::from(v.clone()));
}
if let Some(extra) = &body.extra
&& let Some(obj) = extra.as_object()
{
for (k, v) in obj {
scope.set_extra(k, v.clone());
}
}
},

View File

@@ -21,7 +21,7 @@ pub async fn get_current_user_profile(
true
} else {
user.is_member &&
user.membership_expires_at.map_or(true, |expires| expires > chrono::Utc::now())
user.membership_expires_at.is_none_or(|expires| expires > chrono::Utc::now())
};
Ok(HttpResponse::Ok().json(serde_json::json!({