Skip to main content

crates_docs/cache/
mod.rs

1//! 缓存模块
2//!
3//! 提供内存缓存和 Redis 缓存支持。
4//!
5//! # 特性
6//!
7//! - **内存缓存**: 基于 `moka` 的高性能内存缓存,支持 `TinyLFU` 淘汰策略
8//! - **Redis 缓存**: 支持分布式部署(需要启用 `cache-redis` feature)
9//!
10//! # 示例
11//!
12//! ```rust,no_run
13//! use crates_docs::cache::{Cache, CacheConfig, create_cache};
14//!
15//! let config = CacheConfig::default();
16//! let cache = create_cache(&config).expect("Failed to create cache");
17//! ```
18
19#[cfg(feature = "cache-memory")]
20pub mod memory;
21
22#[cfg(feature = "cache-redis")]
23pub mod redis;
24
25use std::time::Duration;
26
27/// 缓存 trait
28///
29/// 定义缓存操作的基本接口,支持异步读写、TTL 过期和批量清理。
30///
31/// # 实现
32///
33/// - `memory::MemoryCache`: 内存缓存实现
34/// - `redis::RedisCache`: Redis 缓存实现(需要 `cache-redis` feature)
35#[async_trait::async_trait]
36pub trait Cache: Send + Sync {
37    /// 获取缓存值
38    ///
39    /// # 参数
40    ///
41    /// * `key` - 缓存键
42    ///
43    /// # 返回值
44    ///
45    /// 如果键存在且未过期,返回缓存值;否则返回 `None`
46    async fn get(&self, key: &str) -> Option<String>;
47
48    /// 设置缓存值
49    ///
50    /// # 参数
51    ///
52    /// * `key` - 缓存键
53    /// * `value` - 缓存值
54    /// * `ttl` - 可选的过期时间
55    ///
56    /// # 错误
57    ///
58    /// 如果缓存操作失败,返回错误
59    async fn set(
60        &self,
61        key: String,
62        value: String,
63        ttl: Option<Duration>,
64    ) -> crate::error::Result<()>;
65
66    /// 删除缓存值
67    ///
68    /// # 参数
69    ///
70    /// * `key` - 缓存键
71    ///
72    /// # 错误
73    ///
74    /// 如果缓存操作失败,返回错误
75    async fn delete(&self, key: &str) -> crate::error::Result<()>;
76
77    /// 清除所有缓存条目
78    ///
79    /// 仅清除配置了前缀的缓存条目。
80    ///
81    /// # 错误
82    ///
83    /// 如果缓存操作失败,返回错误
84    async fn clear(&self) -> crate::error::Result<()>;
85
86    /// 检查键是否存在
87    ///
88    /// # 参数
89    ///
90    /// * `key` - 缓存键
91    ///
92    /// # 返回值
93    ///
94    /// 如果键存在返回 `true`,否则返回 `false`
95    async fn exists(&self, key: &str) -> bool;
96}
97
98/// 缓存配置
99///
100/// 配置缓存类型、大小、TTL 等参数。
101///
102/// # 字段
103///
104/// - `cache_type`: 缓存类型,`"memory"` 或 `"redis"`
105/// - `memory_size`: 内存缓存大小(条目数)
106/// - `redis_url`: Redis 连接 URL
107/// - `key_prefix`: 键前缀(用于隔离不同服务的缓存)
108/// - `default_ttl`: 默认 TTL(秒)
109/// - `crate_docs_ttl_secs`: crate 文档缓存 TTL(秒)
110/// - `item_docs_ttl_secs`: 项目文档缓存 TTL(秒)
111/// - `search_results_ttl_secs`: 搜索结果缓存 TTL(秒)
112///
113/// # 热重载支持
114///
115/// ## 支持热重载的字段 ✅
116///
117/// TTL 相关字段可以在运行时动态更新(影响新写入的缓存条目):
118/// - `default_ttl`: 默认 TTL(秒)
119/// - `crate_docs_ttl_secs`: crate 文档缓存 TTL(秒)
120/// - `item_docs_ttl_secs`: 项目文档缓存 TTL(秒)
121/// - `search_results_ttl_secs`: 搜索结果缓存 TTL(秒)
122///
123/// ## 不支持热重载的字段 ❌
124///
125/// 以下字段需要重启服务器才能生效:
126/// - `cache_type`: 缓存类型(涉及缓存实例的创建)
127/// - `memory_size`: 内存缓存大小(初始化参数)
128/// - `redis_url`: Redis 连接 URL(连接池初始化)
129/// - `key_prefix`: 缓存键前缀(初始化参数)
130///
131/// 原因:这些配置涉及缓存后端(内存/R)的初始化和连接池创建。
132#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
133pub struct CacheConfig {
134    /// 缓存类型:`memory` 或 `redis`
135    pub cache_type: String,
136
137    /// 内存缓存大小(条目数)
138    pub memory_size: Option<usize>,
139
140    /// Redis 连接 URL
141    pub redis_url: Option<String>,
142
143    /// Redis 缓存键前缀(用于隔离不同服务的缓存条目)
144    #[serde(default = "default_key_prefix")]
145    pub key_prefix: String,
146
147    /// 默认 TTL(秒)
148    pub default_ttl: Option<u64>,
149
150    /// crate 文档缓存 TTL(秒)
151    #[serde(default = "default_crate_docs_ttl")]
152    pub crate_docs_ttl_secs: Option<u64>,
153
154    /// 项目文档缓存 TTL(秒)
155    #[serde(default = "default_item_docs_ttl")]
156    pub item_docs_ttl_secs: Option<u64>,
157
158    /// 搜索结果缓存 TTL(秒)
159    #[serde(default = "default_search_results_ttl")]
160    pub search_results_ttl_secs: Option<u64>,
161}
162
163/// 默认 crate 文档 TTL(1 小时)
164#[must_use]
165pub fn default_crate_docs_ttl() -> Option<u64> {
166    Some(3600)
167}
168
169/// 默认项目文档 TTL(30 分钟)
170#[must_use]
171pub fn default_item_docs_ttl() -> Option<u64> {
172    Some(1800)
173}
174
175/// 默认搜索结果 TTL(5 分钟)
176#[must_use]
177pub fn default_search_results_ttl() -> Option<u64> {
178    Some(300)
179}
180
181/// 默认键前缀
182#[must_use]
183pub fn default_key_prefix() -> String {
184    String::new()
185}
186
187impl Default for CacheConfig {
188    fn default() -> Self {
189        Self {
190            cache_type: "memory".to_string(),
191            memory_size: Some(1000),
192            redis_url: None,
193            key_prefix: String::new(),
194            default_ttl: Some(3600), // 1 hour
195            crate_docs_ttl_secs: default_crate_docs_ttl(),
196            item_docs_ttl_secs: default_item_docs_ttl(),
197            search_results_ttl_secs: default_search_results_ttl(),
198        }
199    }
200}
201
202/// 创建缓存实例
203///
204/// # 参数
205///
206/// * `config` - 缓存配置
207///
208/// # 错误
209///
210/// 如果缓存类型不支持或配置无效,返回错误
211///
212/// # 示例
213///
214/// ```rust,no_run
215/// use crates_docs::cache::{CacheConfig, create_cache};
216///
217/// let config = CacheConfig::default();
218/// let cache = create_cache(&config).expect("Failed to create cache");
219/// ```
220pub fn create_cache(config: &CacheConfig) -> Result<Box<dyn Cache>, crate::error::Error> {
221    match config.cache_type.as_str() {
222        "memory" => {
223            #[cfg(feature = "cache-memory")]
224            {
225                let size = config.memory_size.unwrap_or(1000);
226                Ok(Box::new(memory::MemoryCache::new(size)))
227            }
228            #[cfg(not(feature = "cache-memory"))]
229            {
230                Err(crate::error::Error::config(
231                    "cache_type",
232                    "memory cache feature is not enabled",
233                ))
234            }
235        }
236        "redis" => {
237            #[cfg(feature = "cache-redis")]
238            {
239                // Note: Redis cache requires async initialization, this returns a placeholder
240                // In practice, use the create_cache_async function
241                Err(crate::error::Error::config(
242                    "cache_type",
243                    "Redis cache requires async initialization. Use create_cache_async instead.",
244                ))
245            }
246            #[cfg(not(feature = "cache-redis"))]
247            {
248                Err(crate::error::Error::config(
249                    "cache_type",
250                    "redis cache feature is not enabled",
251                ))
252            }
253        }
254        _ => Err(crate::error::Error::config(
255            "cache_type",
256            format!("unsupported cache type: {}", config.cache_type),
257        )),
258    }
259}
260
261/// 异步创建缓存实例
262///
263/// 支持 Redis 缓存的异步初始化。
264///
265/// # 参数
266///
267/// * `config` - 缓存配置
268///
269/// # 错误
270///
271/// 如果缓存类型不支持或配置无效,返回错误
272///
273/// # 示例
274///
275/// ```rust,no_run
276/// use crates_docs::cache::{CacheConfig, create_cache_async};
277///
278/// #[tokio::main]
279/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
280///     let config = CacheConfig::default();
281///     let cache = create_cache_async(&config).await?;
282///     Ok(())
283/// }
284/// ```
285#[cfg(feature = "cache-redis")]
286pub async fn create_cache_async(
287    config: &CacheConfig,
288) -> Result<Box<dyn Cache>, crate::error::Error> {
289    match config.cache_type.as_str() {
290        "memory" => {
291            let size = config.memory_size.unwrap_or(1000);
292            Ok(Box::new(memory::MemoryCache::new(size)))
293        }
294        "redis" => {
295            let url = config
296                .redis_url
297                .as_ref()
298                .ok_or_else(|| crate::error::Error::config("redis_url", "redis_url is required"))?;
299            Ok(Box::new(
300                redis::RedisCache::new(url, config.key_prefix.clone()).await?,
301            ))
302        }
303        _ => Err(crate::error::Error::config(
304            "cache_type",
305            format!("unsupported cache type: {}", config.cache_type),
306        )),
307    }
308}