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#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
113pub struct CacheConfig {
114    /// 缓存类型:`memory` 或 `redis`
115    pub cache_type: String,
116
117    /// 内存缓存大小(条目数)
118    pub memory_size: Option<usize>,
119
120    /// Redis 连接 URL
121    pub redis_url: Option<String>,
122
123    /// Redis 缓存键前缀(用于隔离不同服务的缓存条目)
124    #[serde(default = "default_key_prefix")]
125    pub key_prefix: String,
126
127    /// 默认 TTL(秒)
128    pub default_ttl: Option<u64>,
129
130    /// crate 文档缓存 TTL(秒)
131    #[serde(default = "default_crate_docs_ttl")]
132    pub crate_docs_ttl_secs: Option<u64>,
133
134    /// 项目文档缓存 TTL(秒)
135    #[serde(default = "default_item_docs_ttl")]
136    pub item_docs_ttl_secs: Option<u64>,
137
138    /// 搜索结果缓存 TTL(秒)
139    #[serde(default = "default_search_results_ttl")]
140    pub search_results_ttl_secs: Option<u64>,
141}
142
143/// 默认 crate 文档 TTL(1 小时)
144#[must_use]
145pub fn default_crate_docs_ttl() -> Option<u64> {
146    Some(3600)
147}
148
149/// 默认项目文档 TTL(30 分钟)
150#[must_use]
151pub fn default_item_docs_ttl() -> Option<u64> {
152    Some(1800)
153}
154
155/// 默认搜索结果 TTL(5 分钟)
156#[must_use]
157pub fn default_search_results_ttl() -> Option<u64> {
158    Some(300)
159}
160
161/// 默认键前缀
162#[must_use]
163pub fn default_key_prefix() -> String {
164    String::new()
165}
166
167impl Default for CacheConfig {
168    fn default() -> Self {
169        Self {
170            cache_type: "memory".to_string(),
171            memory_size: Some(1000),
172            redis_url: None,
173            key_prefix: String::new(),
174            default_ttl: Some(3600), // 1 hour
175            crate_docs_ttl_secs: default_crate_docs_ttl(),
176            item_docs_ttl_secs: default_item_docs_ttl(),
177            search_results_ttl_secs: default_search_results_ttl(),
178        }
179    }
180}
181
182/// 创建缓存实例
183///
184/// # 参数
185///
186/// * `config` - 缓存配置
187///
188/// # 错误
189///
190/// 如果缓存类型不支持或配置无效,返回错误
191///
192/// # 示例
193///
194/// ```rust,no_run
195/// use crates_docs::cache::{CacheConfig, create_cache};
196///
197/// let config = CacheConfig::default();
198/// let cache = create_cache(&config).expect("Failed to create cache");
199/// ```
200pub fn create_cache(config: &CacheConfig) -> Result<Box<dyn Cache>, crate::error::Error> {
201    match config.cache_type.as_str() {
202        "memory" => {
203            #[cfg(feature = "cache-memory")]
204            {
205                let size = config.memory_size.unwrap_or(1000);
206                Ok(Box::new(memory::MemoryCache::new(size)))
207            }
208            #[cfg(not(feature = "cache-memory"))]
209            {
210                Err(crate::error::Error::config(
211                    "cache_type",
212                    "memory cache feature is not enabled",
213                ))
214            }
215        }
216        "redis" => {
217            #[cfg(feature = "cache-redis")]
218            {
219                // Note: Redis cache requires async initialization, this returns a placeholder
220                // In practice, use the create_cache_async function
221                Err(crate::error::Error::config(
222                    "cache_type",
223                    "Redis cache requires async initialization. Use create_cache_async instead.",
224                ))
225            }
226            #[cfg(not(feature = "cache-redis"))]
227            {
228                Err(crate::error::Error::config(
229                    "cache_type",
230                    "redis cache feature is not enabled",
231                ))
232            }
233        }
234        _ => Err(crate::error::Error::config(
235            "cache_type",
236            format!("unsupported cache type: {}", config.cache_type),
237        )),
238    }
239}
240
241/// 异步创建缓存实例
242///
243/// 支持 Redis 缓存的异步初始化。
244///
245/// # 参数
246///
247/// * `config` - 缓存配置
248///
249/// # 错误
250///
251/// 如果缓存类型不支持或配置无效,返回错误
252///
253/// # 示例
254///
255/// ```rust,no_run
256/// use crates_docs::cache::{CacheConfig, create_cache_async};
257///
258/// #[tokio::main]
259/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
260///     let config = CacheConfig::default();
261///     let cache = create_cache_async(&config).await?;
262///     Ok(())
263/// }
264/// ```
265#[cfg(feature = "cache-redis")]
266pub async fn create_cache_async(
267    config: &CacheConfig,
268) -> Result<Box<dyn Cache>, crate::error::Error> {
269    match config.cache_type.as_str() {
270        "memory" => {
271            let size = config.memory_size.unwrap_or(1000);
272            Ok(Box::new(memory::MemoryCache::new(size)))
273        }
274        "redis" => {
275            let url = config
276                .redis_url
277                .as_ref()
278                .ok_or_else(|| crate::error::Error::config("redis_url", "redis_url is required"))?;
279            Ok(Box::new(
280                redis::RedisCache::new(url, config.key_prefix.clone()).await?,
281            ))
282        }
283        _ => Err(crate::error::Error::config(
284            "cache_type",
285            format!("unsupported cache type: {}", config.cache_type),
286        )),
287    }
288}