Files
Aether/crates/aether-wallet/src/quota.rs
T
fawney19 ddf18fed9a feat: 扩展 Rust gateway 全功能模块,新增 billing/crypto/wallet crate 及完整数据层
- 新增 aether-billing、aether-crypto、aether-wallet 独立 crate
- aether-data 扩展 repository 层:announcements、auth_modules、billing、
  candidate_selection、gemini_file_mappings、global_models、management_tokens、
  oauth_providers、proxy_nodes、quota、users、wallet 等模块
- aether-gateway 新增 api/auth/billing/control/middleware/scheduler/usage/
  video_tasks/hooks/maintenance/model_fetch/provider_transport 等功能模块
- 重构 executor decision 和 gateway state 为模块目录结构
- 新增 gateway router、frontdoor 路由层及对应测试
- Python 侧 API 路由重构,新增 compat/support 模块
- 前端 Logo 组件更新及 Provider 管理页面调整
2026-03-31 19:19:04 +08:00

83 lines
2.5 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::quantize_money;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProviderBillingType {
MonthlyQuota,
PayAsYouGo,
FreeTier,
Unknown,
}
impl ProviderBillingType {
pub fn parse(value: &str) -> Self {
match value.trim().to_ascii_lowercase().as_str() {
"monthly_quota" => Self::MonthlyQuota,
"pay_as_you_go" => Self::PayAsYouGo,
"free_tier" => Self::FreeTier,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProviderQuotaSnapshot {
pub provider_id: String,
pub billing_type: ProviderBillingType,
pub monthly_quota_usd: Option<f64>,
pub monthly_used_usd: f64,
pub quota_reset_day: Option<u64>,
pub quota_last_reset_at_unix_secs: Option<u64>,
pub quota_expires_at_unix_secs: Option<u64>,
pub is_active: bool,
}
impl ProviderQuotaSnapshot {
pub fn remaining_quota_usd(&self) -> Option<f64> {
self.monthly_quota_usd
.map(|quota| quantize_money(quota - self.monthly_used_usd))
}
pub fn is_expired(&self, now_unix_secs: u64) -> bool {
self.quota_expires_at_unix_secs
.is_some_and(|expires_at| expires_at <= now_unix_secs)
}
pub fn should_reset(&self, now_unix_secs: u64) -> bool {
if self.billing_type != ProviderBillingType::MonthlyQuota || !self.is_active {
return false;
}
let Some(reset_day) = self.quota_reset_day.filter(|value| *value > 0) else {
return false;
};
let Some(last_reset) = self.quota_last_reset_at_unix_secs else {
return true;
};
now_unix_secs.saturating_sub(last_reset) >= reset_day.saturating_mul(24 * 60 * 60)
}
}
#[cfg(test)]
mod tests {
use super::{ProviderBillingType, ProviderQuotaSnapshot};
#[test]
fn monthly_quota_resets_after_period() {
let snapshot = ProviderQuotaSnapshot {
provider_id: "provider-1".to_string(),
billing_type: ProviderBillingType::MonthlyQuota,
monthly_quota_usd: Some(20.0),
monthly_used_usd: 5.0,
quota_reset_day: Some(7),
quota_last_reset_at_unix_secs: Some(1_000),
quota_expires_at_unix_secs: None,
is_active: true,
};
assert!(!snapshot.should_reset(1_000 + 6 * 24 * 60 * 60));
assert!(snapshot.should_reset(1_000 + 7 * 24 * 60 * 60));
assert_eq!(snapshot.remaining_quota_usd(), Some(15.0));
}
}