//! 应用状态 use sqlx::PgPool; use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; use std::time::Instant; use tokio::sync::RwLock; use tokio_util::sync::CancellationToken; use crate::config::SaaSConfig; use crate::workers::WorkerDispatcher; use crate::cache::AppCache; /// 全局应用状态,通过 Axum State 共享 #[derive(Clone)] pub struct AppState { /// 数据库连接池 pub db: PgPool, /// 服务器配置 (可热更新) pub config: Arc>, /// JWT 密钥 pub jwt_secret: secrecy::SecretString, /// 速率限制: account_id → 请求时间戳列表 pub rate_limit_entries: Arc>>, /// 角色权限缓存: role_id → permissions list pub role_permissions_cache: Arc>>, /// TOTP 失败计数: account_id → (失败次数, 首次失败时间) pub totp_fail_counts: Arc>, /// 无锁 rate limit RPM(从 config 同步,避免每个请求获取 RwLock) rate_limit_rpm: Arc, /// Worker 调度器 (异步后台任务) pub worker_dispatcher: WorkerDispatcher, /// 优雅停机令牌 — 触发后所有 SSE 流和长连接应立即终止 pub shutdown_token: CancellationToken, /// 应用缓存: Model/Provider/队列计数器 pub cache: AppCache, } impl AppState { pub fn new(db: PgPool, config: SaaSConfig, worker_dispatcher: WorkerDispatcher, shutdown_token: CancellationToken) -> anyhow::Result { let jwt_secret = config.jwt_secret()?; let rpm = config.rate_limit.requests_per_minute; Ok(Self { db, config: Arc::new(RwLock::new(config)), jwt_secret, rate_limit_entries: Arc::new(dashmap::DashMap::new()), role_permissions_cache: Arc::new(dashmap::DashMap::new()), totp_fail_counts: Arc::new(dashmap::DashMap::new()), rate_limit_rpm: Arc::new(AtomicU32::new(rpm)), worker_dispatcher, shutdown_token, cache: AppCache::new(), }) } /// 获取当前 rate limit RPM(无锁读取) pub fn rate_limit_rpm(&self) -> u32 { self.rate_limit_rpm.load(Ordering::Relaxed) } /// 更新 rate limit RPM(配置热更新时调用) pub fn set_rate_limit_rpm(&self, rpm: u32) { self.rate_limit_rpm.store(rpm, Ordering::Relaxed); } /// 清理过期的限流条目 /// 使用 3600s 窗口以覆盖 register rate limit (3次/小时) 的完整周期 pub fn cleanup_rate_limit_entries(&self) { let window_start = Instant::now() - std::time::Duration::from_secs(3600); self.rate_limit_entries.retain(|_, entries| { entries.retain(|&ts| ts > window_start); !entries.is_empty() }); } /// 异步派发操作日志到 Worker(非阻塞) pub async fn dispatch_log_operation( &self, account_id: &str, action: &str, target_type: &str, target_id: &str, details: Option, ip_address: Option<&str>, ) { use crate::workers::log_operation::LogOperationArgs; let args = LogOperationArgs { account_id: account_id.to_string(), action: action.to_string(), target_type: target_type.to_string(), target_id: target_id.to_string(), details: details.map(|d| d.to_string()), ip_address: ip_address.map(|s| s.to_string()), }; if let Err(e) = self.worker_dispatcher.dispatch("log_operation", args).await { tracing::warn!("Failed to dispatch log_operation: {}", e); } } }