1use async_trait::async_trait;
8use serde::{Serialize, de::DeserializeOwned};
9use serde_json;
10use alun_core::Result;
11use std::collections::HashMap;
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU64, Ordering};
14use parking_lot::RwLock;
15use std::time::{Instant, Duration};
16use redis::aio::ConnectionManager;
17
18#[async_trait]
30pub trait Cache: Send + Sync {
31 async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>>;
33
34 async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()>;
36
37 async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()>;
39
40 async fn del(&self, key: &str) -> Result<()>;
42
43 async fn exists(&self, key: &str) -> Result<bool>;
45
46 async fn incr(&self, key: &str, delta: i64) -> Result<i64>;
48
49 async fn keys(&self, pattern: &str) -> Result<Vec<String>>;
51
52 async fn delete_pattern(&self, pattern: &str) -> Result<u64>;
54
55 fn stats(&self) -> CacheStats { CacheStats::default() }
57}
58
59struct CacheEntry {
62 value: serde_json::Value,
63 expires_at: Option<Instant>,
64}
65
66#[derive(Debug, Clone, Default)]
70pub struct CacheStats {
71 pub hits: u64,
73 pub misses: u64,
75 pub sets: u64,
77 pub deletes: u64,
79 pub evictions: u64,
81 pub expired_cleanups: u64,
83}
84
85#[derive(Clone)]
87pub struct LocalCache {
88 key_prefix: String,
90 data: Arc<RwLock<HashMap<String, CacheEntry>>>,
92 max_capacity: u64,
94 default_ttl_secs: u64,
96 stats: Arc<AtomicCacheStats>,
98 cleanup_interval_secs: u64,
100}
101
102struct AtomicCacheStats {
103 hits: AtomicU64,
104 misses: AtomicU64,
105 sets: AtomicU64,
106 deletes: AtomicU64,
107 evictions: AtomicU64,
108 expired_cleanups: AtomicU64,
109}
110
111impl Clone for AtomicCacheStats {
112 fn clone(&self) -> Self {
113 Self {
114 hits: AtomicU64::new(self.hits.load(Ordering::Relaxed)),
115 misses: AtomicU64::new(self.misses.load(Ordering::Relaxed)),
116 sets: AtomicU64::new(self.sets.load(Ordering::Relaxed)),
117 deletes: AtomicU64::new(self.deletes.load(Ordering::Relaxed)),
118 evictions: AtomicU64::new(self.evictions.load(Ordering::Relaxed)),
119 expired_cleanups: AtomicU64::new(self.expired_cleanups.load(Ordering::Relaxed)),
120 }
121 }
122}
123
124impl LocalCache {
125 pub fn new(key_prefix: &str, max_capacity: u64, default_ttl_secs: u64) -> Self {
131 Self {
132 key_prefix: key_prefix.to_string(),
133 data: Arc::new(RwLock::new(HashMap::new())),
134 max_capacity,
135 default_ttl_secs,
136 stats: Arc::new(AtomicCacheStats {
137 hits: AtomicU64::new(0),
138 misses: AtomicU64::new(0),
139 sets: AtomicU64::new(0),
140 deletes: AtomicU64::new(0),
141 evictions: AtomicU64::new(0),
142 expired_cleanups: AtomicU64::new(0),
143 }),
144 cleanup_interval_secs: 60,
145 }
146 }
147
148 pub fn with_cleanup_interval(mut self, interval_secs: u64) -> Self {
149 self.cleanup_interval_secs = interval_secs;
150 self
151 }
152
153 pub fn stats(&self) -> CacheStats {
155 CacheStats {
156 hits: self.stats.hits.load(Ordering::Relaxed),
157 misses: self.stats.misses.load(Ordering::Relaxed),
158 sets: self.stats.sets.load(Ordering::Relaxed),
159 deletes: self.stats.deletes.load(Ordering::Relaxed),
160 evictions: self.stats.evictions.load(Ordering::Relaxed),
161 expired_cleanups: self.stats.expired_cleanups.load(Ordering::Relaxed),
162 }
163 }
164
165 pub fn len(&self) -> usize {
167 self.data.read().len()
168 }
169
170 pub fn is_empty(&self) -> bool {
172 self.data.read().is_empty()
173 }
174
175 pub fn cleanup_expired(&self) -> u64 {
177 let mut guard = self.data.write();
178 let expired: Vec<String> = guard.iter()
179 .filter(|(_, entry)| entry.expires_at.map_or(false, |t| Instant::now() > t))
180 .map(|(k, _)| k.clone())
181 .collect();
182 let count = expired.len() as u64;
183 for k in &expired { guard.remove(k); }
184 self.stats.expired_cleanups.fetch_add(count, Ordering::Relaxed);
185 count
186 }
187
188 pub fn start_background_cleanup(&self) {
190 let data = Arc::clone(&self.data);
191 let stats = Arc::clone(&self.stats);
192 let interval = self.cleanup_interval_secs;
193
194 tokio::spawn(async move {
195 loop {
196 tokio::time::sleep(Duration::from_secs(interval)).await;
197 let mut guard = data.write();
198 let now = Instant::now();
199 let expired: Vec<String> = guard.iter()
200 .filter(|(_, entry)| entry.expires_at.map_or(false, |t| now > t))
201 .map(|(k, _)| k.clone())
202 .collect();
203 let count = expired.len() as u64;
204 for k in &expired { guard.remove(k); }
205 if count > 0 {
206 stats.expired_cleanups.fetch_add(count, Ordering::Relaxed);
207 tracing::debug!("缓存后台清理: 移除 {} 个过期条目", count);
208 }
209 }
210 });
211 }
212
213 #[inline]
217 fn prefixed(&self, key: &str) -> String {
218 if self.key_prefix.is_empty() {
219 key.to_string()
220 } else {
221 format!("{}:{}", self.key_prefix, key)
222 }
223 }
224}
225
226#[async_trait]
227impl Cache for LocalCache {
228 async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>> {
229 let key = self.prefixed(key);
230 let guard = self.data.read();
231 if let Some(entry) = guard.get(&key) {
232 if let Some(expires) = entry.expires_at {
233 if Instant::now() > expires {
234 drop(guard);
235 self.data.write().remove(&key);
236 self.stats.misses.fetch_add(1, Ordering::Relaxed);
237 return Ok(None);
238 }
239 }
240 self.stats.hits.fetch_add(1, Ordering::Relaxed);
241 let val: T = serde_json::from_value(entry.value.clone())
242 .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
243 return Ok(Some(val));
244 }
245 self.stats.misses.fetch_add(1, Ordering::Relaxed);
246 Ok(None)
247 }
248
249 async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()> {
250 let key = self.prefixed(key);
251 let v = serde_json::to_value(value)
252 .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
253 let mut guard = self.data.write();
254 if self.max_capacity > 0 && guard.len() as u64 >= self.max_capacity {
255 drop(guard);
256 return Err(alun_core::Error::Msg(format!("缓存容量已达上限: {}", self.max_capacity)));
257 }
258 self.stats.sets.fetch_add(1, Ordering::Relaxed);
259 let expires_at = if self.default_ttl_secs > 0 {
260 Some(Instant::now() + Duration::from_secs(self.default_ttl_secs))
261 } else {
262 None
263 };
264 guard.insert(key, CacheEntry { value: v, expires_at });
265 Ok(())
266 }
267
268 async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
269 let key = self.prefixed(key);
270 let v = serde_json::to_value(value)
271 .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
272 self.stats.sets.fetch_add(1, Ordering::Relaxed);
273 self.data.write().insert(key, CacheEntry {
274 value: v,
275 expires_at: Some(Instant::now() + Duration::from_secs(ttl_secs)),
276 });
277 Ok(())
278 }
279
280 async fn del(&self, key: &str) -> Result<()> {
281 let key = self.prefixed(key);
282 let removed = self.data.write().remove(&key).is_some();
283 if removed { self.stats.deletes.fetch_add(1, Ordering::Relaxed); }
284 Ok(())
285 }
286
287 async fn exists(&self, key: &str) -> Result<bool> {
288 let key = self.prefixed(key);
289 let guard = self.data.read();
290 let found = guard.get(&key).map_or(false, |entry| {
291 entry.expires_at.map_or(true, |exp| Instant::now() <= exp)
292 });
293 if found { self.stats.hits.fetch_add(1, Ordering::Relaxed); }
294 else { self.stats.misses.fetch_add(1, Ordering::Relaxed); }
295 Ok(found)
296 }
297
298 async fn incr(&self, key: &str, delta: i64) -> Result<i64> {
299 let key = self.prefixed(key);
300 let mut guard = self.data.write();
301 let entry = guard.entry(key).or_insert_with(|| CacheEntry {
302 value: serde_json::Value::Number(serde_json::Number::from(0i64)),
303 expires_at: None,
304 });
305 let current = entry.value.as_i64().unwrap_or(0);
306 let new_val = current + delta;
307 entry.value = serde_json::Value::Number(serde_json::Number::from(new_val));
308 Ok(new_val)
309 }
310
311 async fn keys(&self, pattern: &str) -> Result<Vec<String>> {
312 let prefixed_pattern = self.prefixed(pattern);
313 let guard = self.data.read();
314 let prefix = &self.key_prefix;
315 let strip_len = if prefix.is_empty() { 0 } else { prefix.len() + 1 };
316 Ok(guard.keys()
317 .filter(|k| match_pattern(k, &prefixed_pattern))
318 .map(|k| if strip_len > 0 && k.len() > strip_len { k[strip_len..].to_string() } else { k.clone() })
319 .collect())
320 }
321
322 async fn delete_pattern(&self, pattern: &str) -> Result<u64> {
323 let prefixed_pattern = self.prefixed(pattern);
324 let mut guard = self.data.write();
325 let to_remove: Vec<String> = guard.keys()
326 .filter(|k| match_pattern(k, &prefixed_pattern))
327 .cloned()
328 .collect();
329 let count = to_remove.len() as u64;
330 for k in to_remove { guard.remove(&k); }
331 Ok(count)
332 }
333}
334
335#[derive(Clone)]
339pub struct RedisCache {
340 key_prefix: String,
342 conn: ConnectionManager,
344}
345
346impl RedisCache {
347 pub fn new(key_prefix: &str, conn: ConnectionManager) -> Self {
351 Self { key_prefix: key_prefix.to_string(), conn }
352 }
353
354 pub async fn connect(key_prefix: &str, url: &str) -> Result<Self> {
358 let client = redis::Client::open(url)
359 .map_err(|e| alun_core::Error::Config(format!("Redis URL 无效: {}", e)))?;
360 let conn = ConnectionManager::new(client).await
361 .map_err(|e| alun_core::Error::Config(format!("Redis 连接失败: {}", e)))?;
362 Ok(Self { key_prefix: key_prefix.to_string(), conn })
363 }
364
365 #[inline]
369 fn prefixed(&self, key: &str) -> String {
370 if self.key_prefix.is_empty() {
371 key.to_string()
372 } else {
373 format!("{}:{}", self.key_prefix, key)
374 }
375 }
376
377 fn map_err(e: redis::RedisError) -> alun_core::Error {
378 alun_core::Error::Msg(e.to_string())
379 }
380}
381
382#[async_trait]
383impl Cache for RedisCache {
384 async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>> {
385 let key = self.prefixed(key);
386 let result: Option<String> = redis::cmd("GET")
387 .arg(&key)
388 .query_async(&mut self.conn.clone())
389 .await
390 .map_err(Self::map_err)?;
391
392 if let Some(json) = result {
393 let val: T = serde_json::from_str(&json)
394 .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
395 Ok(Some(val))
396 } else {
397 Ok(None)
398 }
399 }
400
401 async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()> {
402 let key = self.prefixed(key);
403 let json = serde_json::to_string(value)
404 .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
405 redis::cmd("SET")
406 .arg(&key).arg(&json)
407 .query_async::<()>(&mut self.conn.clone())
408 .await
409 .map_err(Self::map_err)
410 }
411
412 async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
413 let key = self.prefixed(key);
414 let json = serde_json::to_string(value)
415 .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
416 redis::cmd("SETEX")
417 .arg(&key).arg(ttl_secs).arg(&json)
418 .query_async::<()>(&mut self.conn.clone())
419 .await
420 .map_err(Self::map_err)
421 }
422
423 async fn del(&self, key: &str) -> Result<()> {
424 let key = self.prefixed(key);
425 redis::cmd("DEL")
426 .arg(&key)
427 .query_async::<()>(&mut self.conn.clone())
428 .await
429 .map_err(Self::map_err)
430 }
431
432 async fn exists(&self, key: &str) -> Result<bool> {
433 let key = self.prefixed(key);
434 redis::cmd("EXISTS")
435 .arg(&key)
436 .query_async::<i32>(&mut self.conn.clone())
437 .await
438 .map_err(Self::map_err)
439 .map(|v| v > 0)
440 }
441
442 async fn incr(&self, key: &str, delta: i64) -> Result<i64> {
443 let key = self.prefixed(key);
444 let result: i64 = if delta == 1 {
445 redis::cmd("INCR")
446 .arg(&key)
447 .query_async(&mut self.conn.clone())
448 .await
449 .map_err(Self::map_err)?
450 } else {
451 redis::cmd("INCRBY")
452 .arg(&key).arg(delta)
453 .query_async(&mut self.conn.clone())
454 .await
455 .map_err(Self::map_err)?
456 };
457 Ok(result)
458 }
459
460 async fn keys(&self, pattern: &str) -> Result<Vec<String>> {
461 let prefixed_pattern = self.prefixed(pattern);
462 let prefix = &self.key_prefix;
463 let strip_len = if prefix.is_empty() { 0 } else { prefix.len() + 1 };
464 redis::cmd("KEYS")
465 .arg(&prefixed_pattern)
466 .query_async::<Vec<String>>(&mut self.conn.clone())
467 .await
468 .map_err(Self::map_err)
469 .map(|keys| {
470 keys.into_iter()
471 .map(|k| if strip_len > 0 && k.len() > strip_len { k[strip_len..].to_string() } else { k })
472 .collect()
473 })
474 }
475
476 async fn delete_pattern(&self, pattern: &str) -> Result<u64> {
477 let keys: Vec<String> = self.keys(pattern).await?;
478 if keys.is_empty() { return Ok(0); }
479 let mut cmd = redis::cmd("DEL");
480 for k in &keys {
481 cmd.arg(self.prefixed(k));
482 }
483 cmd.query_async::<u64>(&mut self.conn.clone())
484 .await
485 .map_err(Self::map_err)
486 }
487}
488
489fn match_pattern(key: &str, pattern: &str) -> bool {
492 if pattern.is_empty() { return key.is_empty(); }
493 match_pattern_rec(key.as_bytes(), 0, pattern.as_bytes(), 0)
494}
495
496fn match_pattern_rec(key: &[u8], ki: usize, pat: &[u8], pi: usize) -> bool {
497 if ki >= key.len() && pi >= pat.len() { return true; }
498 if pi >= pat.len() { return false; }
499 match pat[pi] {
500 b'*' => {
501 if pi + 1 >= pat.len() { return true; }
502 for nk in ki..=key.len() {
503 if match_pattern_rec(key, nk, pat, pi + 1) { return true; }
504 }
505 false
506 }
507 b'?' => {
508 ki < key.len() && match_pattern_rec(key, ki + 1, pat, pi + 1)
509 }
510 c => {
511 ki < key.len() && key[ki] == c && match_pattern_rec(key, ki + 1, pat, pi + 1)
512 }
513 }
514}
515
516#[derive(Clone)]
520pub enum SharedCache {
521 Local(LocalCache),
522 Redis(RedisCache),
523}
524
525#[async_trait]
526impl Cache for SharedCache {
527 async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>> {
528 match self {
529 SharedCache::Local(c) => c.get(key).await,
530 SharedCache::Redis(c) => c.get(key).await,
531 }
532 }
533
534 async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()> {
535 match self {
536 SharedCache::Local(c) => c.set(key, value).await,
537 SharedCache::Redis(c) => c.set(key, value).await,
538 }
539 }
540
541 async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
542 match self {
543 SharedCache::Local(c) => c.set_ex(key, value, ttl_secs).await,
544 SharedCache::Redis(c) => c.set_ex(key, value, ttl_secs).await,
545 }
546 }
547
548 async fn del(&self, key: &str) -> Result<()> {
549 match self {
550 SharedCache::Local(c) => c.del(key).await,
551 SharedCache::Redis(c) => c.del(key).await,
552 }
553 }
554
555 async fn exists(&self, key: &str) -> Result<bool> {
556 match self { SharedCache::Local(c) => c.exists(key).await, SharedCache::Redis(c) => c.exists(key).await }
557 }
558
559 async fn incr(&self, key: &str, delta: i64) -> Result<i64> {
560 match self { SharedCache::Local(c) => c.incr(key, delta).await, SharedCache::Redis(c) => c.incr(key, delta).await }
561 }
562
563 async fn keys(&self, pattern: &str) -> Result<Vec<String>> {
564 match self { SharedCache::Local(c) => c.keys(pattern).await, SharedCache::Redis(c) => c.keys(pattern).await }
565 }
566
567 async fn delete_pattern(&self, pattern: &str) -> Result<u64> {
568 match self { SharedCache::Local(c) => c.delete_pattern(pattern).await, SharedCache::Redis(c) => c.delete_pattern(pattern).await }
569 }
570}
571
572pub async fn create_cache(app_name: &str, cache_config: &alun_config::CacheConfig, redis_config: &alun_config::RedisConfig) -> Result<SharedCache> {
580 match cache_config.r#type.as_str() {
581 "redis" => {
582 tracing::info!("使用 Redis 缓存 url={} app={}", redis_config.url, app_name);
583 Ok(SharedCache::Redis(RedisCache::connect(app_name, &redis_config.url).await?))
584 }
585 _ => {
586 tracing::info!("使用本地缓存 capacity={} app={}", cache_config.max_capacity, app_name);
587 Ok(SharedCache::Local(LocalCache::new(app_name, cache_config.max_capacity, cache_config.default_ttl)))
588 }
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595
596 #[tokio::test]
597 async fn test_local_cache_get_set() {
598 let c = LocalCache::new("test",100, 0);
599 c.set("key1", &"value1".to_string()).await.unwrap();
600 let val: Option<String> = c.get("key1").await.unwrap();
601 assert_eq!(val, Some("value1".to_string()));
602 c.del("key1").await.unwrap();
603 let val: Option<String> = c.get("key1").await.unwrap();
604 assert_eq!(val, None);
605 }
606
607 #[tokio::test]
608 async fn test_set_ex_expiration() {
609 let c = LocalCache::new("test",100, 0);
610 c.set_ex("temp", &"expire".to_string(), 1).await.unwrap();
611 let val: Option<String> = c.get("temp").await.unwrap();
612 assert_eq!(val, Some("expire".to_string()));
613 tokio::time::sleep(Duration::from_secs(2)).await;
614 let val: Option<String> = c.get("temp").await.unwrap();
615 assert_eq!(val, None);
616 }
617
618 #[tokio::test]
619 async fn test_incr() {
620 let c = LocalCache::new("test",100, 0);
621 assert_eq!(c.incr("counter", 1).await.unwrap(), 1);
622 assert_eq!(c.incr("counter", 5).await.unwrap(), 6);
623 assert_eq!(c.incr("counter", -2).await.unwrap(), 4);
624 }
625
626 #[tokio::test]
627 async fn test_keys_pattern() {
628 let c = LocalCache::new("test",100, 0);
629 c.set("user:1", &"alice").await.unwrap();
630 c.set("user:2", &"bob").await.unwrap();
631 c.set("order:1", &"o1").await.unwrap();
632 let keys = c.keys("user:*").await.unwrap();
633 assert_eq!(keys.len(), 2);
634 assert!(keys.contains(&"user:1".to_string()));
635 assert!(keys.contains(&"user:2".to_string()));
636 }
637
638 #[tokio::test]
639 async fn test_delete_pattern() {
640 let c = LocalCache::new("test",100, 0);
641 c.set("session:a", &"s1").await.unwrap();
642 c.set("session:b", &"s2").await.unwrap();
643 c.set("user:1", &"alice").await.unwrap();
644 let deleted = c.delete_pattern("session:*").await.unwrap();
645 assert_eq!(deleted, 2);
646 assert!(!c.exists("session:a").await.unwrap());
647 assert!(!c.exists("session:b").await.unwrap());
648 assert!(c.exists("user:1").await.unwrap());
649 }
650}