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 管理页面调整
This commit is contained in:
fawney19
2026-03-31 19:19:04 +08:00
parent b5a0070023
commit ddf18fed9a
690 changed files with 235085 additions and 16299 deletions
+116
View File
@@ -0,0 +1,116 @@
use aether_data::repository::wallet::StoredWalletSnapshot;
use aether_wallet::{
WalletAccessDecision, WalletAccessFailure, WalletLimitMode, WalletSnapshot, WalletStatus,
};
use crate::gateway::data::StoredGatewayAuthApiKeySnapshot;
use crate::gateway::{AppState, GatewayError, GatewayLocalAuthRejection};
pub(crate) async fn resolve_wallet_auth_gate(
state: &AppState,
auth_snapshot: &StoredGatewayAuthApiKeySnapshot,
) -> Result<Option<WalletAccessDecision>, GatewayError> {
if !state.has_wallet_data_reader() {
return Ok(None);
}
let wallet = state
.read_wallet_snapshot_for_auth(
&auth_snapshot.user_id,
&auth_snapshot.api_key_id,
auth_snapshot.api_key_is_standalone,
)
.await?;
let is_admin = auth_snapshot.user_role.eq_ignore_ascii_case("admin");
Ok(Some(match wallet.as_ref() {
Some(wallet) => map_wallet_snapshot(wallet).access_decision(is_admin),
None if is_admin => WalletAccessDecision::allowed(None),
None => WalletAccessDecision::wallet_unavailable(None),
}))
}
pub(crate) fn local_rejection_from_wallet_access(
decision: &WalletAccessDecision,
) -> Option<GatewayLocalAuthRejection> {
match decision.failure.as_ref() {
Some(WalletAccessFailure::WalletUnavailable) => {
Some(GatewayLocalAuthRejection::WalletUnavailable)
}
Some(WalletAccessFailure::BalanceDenied) => {
Some(GatewayLocalAuthRejection::BalanceDenied {
remaining: decision.remaining,
})
}
None => None,
}
}
fn map_wallet_snapshot(snapshot: &StoredWalletSnapshot) -> WalletSnapshot {
WalletSnapshot {
wallet_id: snapshot.id.clone(),
user_id: snapshot.user_id.clone(),
api_key_id: snapshot.api_key_id.clone(),
recharge_balance: snapshot.balance,
gift_balance: snapshot.gift_balance,
limit_mode: WalletLimitMode::parse(&snapshot.limit_mode),
currency: snapshot.currency.clone(),
status: WalletStatus::parse(&snapshot.status),
}
}
#[cfg(test)]
mod tests {
use aether_data::repository::wallet::StoredWalletSnapshot;
use aether_wallet::{WalletAccessFailure, WalletLimitMode, WalletSnapshot, WalletStatus};
use super::{local_rejection_from_wallet_access, map_wallet_snapshot};
use crate::gateway::GatewayLocalAuthRejection;
#[test]
fn maps_wallet_snapshot_and_derives_balance_denied() {
let stored = StoredWalletSnapshot::new(
"wallet-1".to_string(),
Some("user-1".to_string()),
None,
0.0,
0.0,
"finite".to_string(),
"USD".to_string(),
"active".to_string(),
0.0,
0.0,
0.0,
0.0,
100,
)
.expect("wallet should build");
let decision = map_wallet_snapshot(&stored).access_decision(false);
assert_eq!(decision.failure, Some(WalletAccessFailure::BalanceDenied));
assert_eq!(
local_rejection_from_wallet_access(&decision),
Some(GatewayLocalAuthRejection::BalanceDenied {
remaining: Some(0.0),
})
);
}
#[test]
fn unlimited_admin_wallet_gate_allows_without_remaining() {
let decision = WalletSnapshot {
wallet_id: "wallet-1".to_string(),
user_id: Some("user-1".to_string()),
api_key_id: None,
recharge_balance: 0.0,
gift_balance: 0.0,
limit_mode: WalletLimitMode::Unlimited,
currency: "USD".to_string(),
status: WalletStatus::Active,
}
.access_decision(true);
assert!(decision.allowed);
assert_eq!(decision.remaining, None);
}
}