Skip to main content

x_one/xcache/
cache.rs

1//! 本地缓存封装
2//!
3//! 基于 moka 的类型安全缓存,支持泛型存取和 per-entry TTL。
4
5use crate::xutil;
6use moka::Expiry;
7use moka::sync::Cache as MokaCache;
8use std::any::Any;
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11
12/// 缓存条目(值 + 可选 per-entry TTL)
13#[derive(Clone)]
14struct CacheEntry {
15    value: Arc<dyn Any + Send + Sync>,
16    /// 自定义 TTL,None 时使用全局 time_to_live
17    custom_ttl: Option<Duration>,
18}
19
20/// Per-entry 过期策略
21///
22/// 有自定义 TTL 时使用它,否则返回 None 使用全局 time_to_live。
23struct PerEntryExpiry;
24
25impl Expiry<String, CacheEntry> for PerEntryExpiry {
26    fn expire_after_create(
27        &self,
28        _key: &String,
29        value: &CacheEntry,
30        _current_time: Instant,
31    ) -> Option<Duration> {
32        value.custom_ttl
33    }
34
35    fn expire_after_update(
36        &self,
37        _key: &String,
38        value: &CacheEntry,
39        _updated_at: Instant,
40        _duration_until_expiry: Option<Duration>,
41    ) -> Option<Duration> {
42        value.custom_ttl
43    }
44}
45
46/// 本地缓存实例
47///
48/// 基于 moka::sync::Cache 封装,通过类型擦除支持任意类型的存取。
49///
50/// ```ignore
51/// use x_one::xcache;
52///
53/// // 通过全局便捷 API 操作默认缓存
54/// xcache::set("user:123", "Alice".to_string());
55/// let name: Option<String> = xcache::get("user:123");
56///
57/// // 获取命名缓存实例做更精细操作
58/// if let Some(cache) = xcache::c("session") {
59///     cache.set_with_ttl("token", "abc", Duration::from_secs(300));
60/// }
61/// ```
62pub struct Cache {
63    inner: MokaCache<String, CacheEntry>,
64    default_ttl: Duration,
65}
66
67impl Cache {
68    /// 创建新的缓存实例
69    pub fn new(max_capacity: u64, default_ttl: Duration) -> Self {
70        let inner = MokaCache::builder()
71            .max_capacity(max_capacity)
72            .time_to_live(default_ttl)
73            .expire_after(PerEntryExpiry)
74            .build();
75
76        Self { inner, default_ttl }
77    }
78
79    /// 获取缓存值(泛型,通过 downcast 类型转换)
80    ///
81    /// 如果 key 存在但类型不匹配,返回 None。
82    pub fn get<V: Any + Clone + Send + Sync>(&self, key: &str) -> Option<V> {
83        let entry = self.inner.get(key)?;
84        let result = entry.value.downcast_ref::<V>().cloned();
85        if result.is_none() {
86            xutil::warn_if_enable_debug(&format!(
87                "xcache: type mismatch for key=[{key}], expected=[{}]",
88                std::any::type_name::<V>()
89            ));
90        }
91        result
92    }
93
94    /// 设置缓存值(使用默认 TTL)
95    pub fn set<V: Any + Send + Sync + 'static>(&self, key: &str, value: V) {
96        self.inner.insert(
97            key.to_string(),
98            CacheEntry {
99                value: Arc::new(value),
100                custom_ttl: None,
101            },
102        );
103    }
104
105    /// 设置缓存值(指定 TTL)
106    ///
107    /// 通过 moka Expiry trait 实现 per-entry TTL,
108    /// 该条目会在指定 TTL 后过期,而非使用全局默认 TTL。
109    pub fn set_with_ttl<V: Any + Send + Sync + 'static>(&self, key: &str, value: V, ttl: Duration) {
110        self.inner.insert(
111            key.to_string(),
112            CacheEntry {
113                value: Arc::new(value),
114                custom_ttl: Some(ttl),
115            },
116        );
117    }
118
119    /// 删除缓存条目
120    pub fn del(&self, key: &str) {
121        self.inner.invalidate(key);
122    }
123
124    /// 获取缓存条目数
125    pub fn entry_count(&self) -> u64 {
126        self.inner.entry_count()
127    }
128
129    /// 获取默认 TTL
130    pub fn default_ttl(&self) -> Duration {
131        self.default_ttl
132    }
133}
134
135impl std::fmt::Debug for Cache {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_struct("Cache")
138            .field("entry_count", &self.inner.entry_count())
139            .field("default_ttl", &self.default_ttl)
140            .finish()
141    }
142}