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}