use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::RwLock; use serde::{Serialize, Deserialize}; /// Cache entry avec expiration #[derive(Debug, Clone)] pub struct CacheEntry { pub value: T, pub expires_at: Instant, pub hit_count: u64, pub last_accessed: Instant, } impl CacheEntry { pub fn new(value: T, ttl: Duration) -> Self { let now = Instant::now(); Self { value, expires_at: now + ttl, hit_count: 0, last_accessed: now, } } pub fn is_expired(&self) -> bool { Instant::now() > self.expires_at } pub fn touch(&mut self) { self.hit_count += 1; self.last_accessed = Instant::now(); } } /// Cache intelligent avec LRU et expiration pub struct SmartCache where K: Clone + std::hash::Hash + Eq, V: Clone, { entries: Arc>>>, max_size: usize, default_ttl: Duration, } impl SmartCache where K: Clone + std::hash::Hash + Eq, V: Clone, { pub fn new(max_size: usize, default_ttl: Duration) -> Self { Self { entries: Arc::new(RwLock::new(HashMap::new())), max_size, default_ttl, } } /// Insère une valeur dans le cache pub async fn insert(&self, key: K, value: V) { self.insert_with_ttl(key, value, self.default_ttl).await; } /// Insère une valeur avec un TTL personnalisé pub async fn insert_with_ttl(&self, key: K, value: V, ttl: Duration) { let mut entries = self.entries.write().await; // Nettoyage des entrées expirées self.cleanup_expired(&mut entries).await; // Éviction LRU si le cache est plein if entries.len() >= self.max_size { self.evict_lru(&mut entries).await; } entries.insert(key, CacheEntry::new(value, ttl)); } /// Récupère une valeur du cache pub async fn get(&self, key: &K) -> Option { let mut entries = self.entries.write().await; if let Some(entry) = entries.get_mut(key) { if entry.is_expired() { entries.remove(key); return None; } entry.touch(); Some(entry.value.clone()) } else { None } } /// Supprime une entrée du cache pub async fn remove(&self, key: &K) -> Option { let mut entries = self.entries.write().await; entries.remove(key).map(|entry| entry.value) } /// Nettoie les entrées expirées async fn cleanup_expired(&self, entries: &mut HashMap>) { let expired_keys: Vec = entries.iter() .filter(|(_, entry)| entry.is_expired()) .map(|(key, _)| key.clone()) .collect(); for key in expired_keys { entries.remove(&key); } } /// Éviction LRU (Least Recently Used) async fn evict_lru(&self, entries: &mut HashMap>) { if let Some((lru_key, _)) = entries.iter() .min_by_key(|(_, entry)| entry.last_accessed) .map(|(key, entry)| (key.clone(), entry.clone())) { entries.remove(&lru_key); } } /// Statistiques du cache pub async fn stats(&self) -> CacheStats { let entries = self.entries.read().await; let total_hits: u64 = entries.values().map(|entry| entry.hit_count).sum(); CacheStats { total_entries: entries.len(), max_size: self.max_size, total_hits, hit_rate: if entries.is_empty() { 0.0 } else { total_hits as f64 / entries.len() as f64 }, } } /// Vide le cache pub async fn clear(&self) { let mut entries = self.entries.write().await; entries.clear(); } } #[derive(Debug, Clone, Serialize)] pub struct CacheStats { pub total_entries: usize, pub max_size: usize, pub total_hits: u64, pub hit_rate: f64, } /// Cache spécialisé pour les messages de salon pub type RoomMessageCache = SmartCache>; /// Cache spécialisé pour les messages directs pub type DirectMessageCache = SmartCache<(i32, i32), Vec>; /// Cache spécialisé pour les utilisateurs en ligne pub type UserPresenceCache = SmartCache; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MessageCacheEntry { pub id: i32, pub user_id: i32, pub username: String, pub content: String, pub timestamp: chrono::DateTime, pub message_type: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserPresenceEntry { pub user_id: i32, pub username: String, pub status: String, pub last_seen: chrono::DateTime, pub current_room: Option, } /// Gestionnaire centralisé de tous les caches pub struct CacheManager { pub room_messages: RoomMessageCache, pub direct_messages: DirectMessageCache, pub user_presence: UserPresenceCache, pub user_sessions: SmartCache, // JWT token -> user_id } impl Default for CacheManager { fn default() -> Self { Self::new() } } impl CacheManager { pub fn new() -> Self { Self { // Cache des messages de salon (30 min TTL) room_messages: SmartCache::new(1000, Duration::from_secs(1800)), // Cache des messages directs (1 heure TTL) direct_messages: SmartCache::new(500, Duration::from_secs(3600)), // Cache de présence utilisateur (5 min TTL) user_presence: SmartCache::new(10000, Duration::from_secs(300)), // Cache des sessions JWT (24 heures TTL) user_sessions: SmartCache::new(50000, Duration::from_secs(86400)), } } /// Met en cache les messages d'un salon pub async fn cache_room_messages(&self, room: &str, messages: Vec) { self.room_messages.insert(room.to_string(), messages).await; } /// Récupère les messages mis en cache d'un salon pub async fn get_cached_room_messages(&self, room: &str) -> Option> { self.room_messages.get(&room.to_string()).await } /// Met en cache les messages directs entre deux utilisateurs pub async fn cache_direct_messages(&self, user1: i32, user2: i32, messages: Vec) { // Normaliser la clé pour éviter les doublons (user1, user2) et (user2, user1) let key = if user1 < user2 { (user1, user2) } else { (user2, user1) }; self.direct_messages.insert(key, messages).await; } /// Récupère les messages directs mis en cache pub async fn get_cached_direct_messages(&self, user1: i32, user2: i32) -> Option> { let key = if user1 < user2 { (user1, user2) } else { (user2, user1) }; self.direct_messages.get(&key).await } /// Met en cache la présence d'un utilisateur pub async fn cache_user_presence(&self, user_id: i32, presence: UserPresenceEntry) { self.user_presence.insert(user_id, presence).await; } /// Récupère la présence mise en cache d'un utilisateur pub async fn get_cached_user_presence(&self, user_id: i32) -> Option { self.user_presence.get(&user_id).await } /// Met en cache une session utilisateur pub async fn cache_user_session(&self, token: &str, user_id: i32) { self.user_sessions.insert(token.to_string(), user_id).await; } /// Récupère l'ID utilisateur d'un token mis en cache pub async fn get_cached_user_session(&self, token: &str) -> Option { self.user_sessions.get(&token.to_string()).await } /// Invalide la session d'un utilisateur pub async fn invalidate_user_session(&self, token: &str) { self.user_sessions.remove(&token.to_string()).await; } /// Nettoie tous les caches expirés pub async fn cleanup_all(&self) { // Le nettoyage est automatique lors des opérations get/insert tracing::info!("🧹 Nettoyage automatique des caches effectué"); } /// Statistiques globales des caches pub async fn global_stats(&self) -> GlobalCacheStats { let room_stats = self.room_messages.stats().await; let dm_stats = self.direct_messages.stats().await; let presence_stats = self.user_presence.stats().await; let session_stats = self.user_sessions.stats().await; GlobalCacheStats { room_messages: room_stats, direct_messages: dm_stats, user_presence: presence_stats, user_sessions: session_stats, } } /// Vide tous les caches (pour le débogage/maintenance) pub async fn clear_all(&self) { self.room_messages.clear().await; self.direct_messages.clear().await; self.user_presence.clear().await; self.user_sessions.clear().await; tracing::warn!("🗑️ Tous les caches ont été vidés"); } } #[derive(Debug, Serialize)] pub struct GlobalCacheStats { pub room_messages: CacheStats, pub direct_messages: CacheStats, pub user_presence: CacheStats, pub user_sessions: CacheStats, }