Implement compression quota refunds and admin manual subscription

This commit is contained in:
2025-12-19 23:28:32 +08:00
commit 11f48fd3dd
106 changed files with 27848 additions and 0 deletions

73
src/services/quota.rs Normal file
View File

@@ -0,0 +1,73 @@
use crate::error::{AppError, ErrorCode};
use crate::state::AppState;
use chrono::{Duration, Utc};
use std::net::IpAddr;
pub async fn consume_anonymous_units(
state: &AppState,
session_id: &str,
ip: IpAddr,
units: u32,
) -> Result<(), AppError> {
if units == 0 {
return Ok(());
}
let date = utc8_date();
let session_key = format!("anon_quota:{session_id}:{date}");
let ip_key = format!("anon_quota_ip:{ip}:{date}");
let mut conn = state.redis.clone();
let limit = state.config.anon_daily_units as i64;
let ttl_seconds = 48 * 60 * 60;
let inc = units as i64;
let script = redis::Script::new(
r#"
local limit = tonumber(ARGV[1])
local ttl = tonumber(ARGV[2])
local inc = tonumber(ARGV[3])
local v1 = tonumber(redis.call('GET', KEYS[1]) or '0')
local v2 = tonumber(redis.call('GET', KEYS[2]) or '0')
if v1 + inc > limit or v2 + inc > limit then
return -1
end
v1 = redis.call('INCRBY', KEYS[1], inc)
v2 = redis.call('INCRBY', KEYS[2], inc)
if v1 == inc then redis.call('EXPIRE', KEYS[1], ttl) end
if v2 == inc then redis.call('EXPIRE', KEYS[2], ttl) end
return v1
"#,
);
let new_value: i64 = script
.key(session_key)
.key(ip_key)
.arg(limit)
.arg(ttl_seconds)
.arg(inc)
.invoke_async(&mut conn)
.await
.map_err(|err| AppError::new(ErrorCode::Internal, "匿名配额检查失败").with_source(err))?;
if new_value < 0 {
return Err(AppError::new(
ErrorCode::QuotaExceeded,
"匿名试用次数已用完(每日 10 次)",
));
}
Ok(())
}
fn utc8_date() -> String {
let now = Utc::now() + Duration::hours(8);
now.format("%Y-%m-%d").to_string()
}