Implement compression quota refunds and admin manual subscription
This commit is contained in:
73
src/services/quota.rs
Normal file
73
src/services/quota.rs
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user