secra_cache/plugin.rs
1use async_trait::async_trait;
2use redis::AsyncCommands;
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5/// PluginCache 实现
6use std::sync::Arc;
7use std::time::Duration;
8
9use super::error::CacheError;
10use super::manager::CacheManager;
11use super::traits::Cache;
12
13/// 插件缓存实例
14///
15/// 每个插件拥有一个独立的 PluginCache 实例,所有操作自动添加命名空间前缀
16pub struct PluginCache {
17 /// CacheManager 引用
18 manager: Arc<CacheManager>,
19
20 /// 插件 ID
21 plugin_id: String,
22
23 /// 命名空间前缀
24 namespace: String,
25}
26
27impl PluginCache {
28 /// 创建新的 PluginCache 实例
29 ///
30 /// 为指定的插件创建一个独立的缓存实例,所有操作会自动添加命名空间前缀。
31 /// 命名空间格式为:`{system_name}:plugin:{plugin_id}:`
32 ///
33 /// # Arguments
34 /// * `manager` - CacheManager 的引用,用于管理 Redis 连接和配置
35 /// * `plugin_id` - 插件的唯一标识符,用于构建命名空间前缀
36 ///
37 /// # Returns
38 /// * `Self` - 新创建的 PluginCache 实例
39 ///
40 /// # 命名空间隔离
41 /// 每个插件拥有独立的命名空间,确保不同插件的缓存数据完全隔离,
42 /// 防止 Key 冲突和跨插件访问。
43 ///
44 /// # Example
45 /// ```no_run
46 /// # use secra_cache::*;
47 /// # use std::sync::Arc;
48 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
49 /// # let manager = todo!();
50 /// let plugin_cache = PluginCache::new(manager, "my_plugin".to_string());
51 /// # Ok(())
52 /// # }
53 /// ```
54 pub(crate) fn new(manager: Arc<CacheManager>, plugin_id: String) -> Self {
55 let namespace = format!("{}:plugin:{}:", manager.system_name(), plugin_id);
56 Self {
57 manager,
58 plugin_id,
59 namespace,
60 }
61 }
62
63 /// 构建完整的 Key(添加命名空间前缀)
64 ///
65 /// 将业务 Key 与插件的命名空间前缀组合,生成完整的缓存 Key。
66 /// 在组合之前会先验证业务 Key 的格式。
67 ///
68 /// # Arguments
69 /// * `business_key` - 业务 Key(不包含命名空间前缀)
70 ///
71 /// # Returns
72 /// * `Ok(String)` - 完整的缓存 Key(包含命名空间前缀)
73 /// * `Err(CacheError::InvalidKey)` - 业务 Key 格式验证失败
74 ///
75 /// # 性能优化
76 /// * 使用 `String::with_capacity` 预分配容量,减少内存重新分配
77 /// * 容量计算为命名空间长度 + 业务 Key 长度,避免扩容
78 ///
79 /// # 安全性
80 /// * 在构建 Key 之前会调用 `validate_business_key` 进行格式验证
81 /// * 确保业务 Key 符合安全规范,防止注入攻击
82 ///
83 /// # Example
84 /// 如果命名空间为 `system:plugin:my_plugin:`,业务 Key 为 `user:123`,
85 /// 则返回的完整 Key 为 `system:plugin:my_plugin:user:123`
86 fn build_key(&self, business_key: &str) -> Result<String, CacheError> {
87 // 验证业务 Key 格式
88 self.validate_business_key(business_key)?;
89
90 // 性能优化:预分配容量,减少内存重新分配
91 let mut full_key = String::with_capacity(self.namespace.len() + business_key.len());
92 full_key.push_str(&self.namespace);
93 full_key.push_str(business_key);
94 Ok(full_key)
95 }
96
97 /// 验证业务 Key 格式
98 ///
99 /// 对业务 Key 进行格式验证,确保符合安全规范和性能要求。
100 /// 验证规则包括:非空、长度限制、字符限制、命名空间前缀检查。
101 ///
102 /// # Arguments
103 /// * `key` - 待验证的业务 Key
104 ///
105 /// # Returns
106 /// * `Ok(())` - Key 格式验证通过
107 /// * `Err(CacheError::InvalidKey)` - Key 格式验证失败,包含具体错误信息
108 ///
109 /// # 验证规则
110 /// 1. **非空检查**:Key 不能为空字符串
111 /// 2. **长度限制**:Key 长度不能超过 200 字符(防止过长的 Key 影响性能)
112 /// 3. **字符限制**:只允许 ASCII 字母、数字、下划线(`_`)、连字符(`-`)、冒号(`:`)
113 /// 4. **命名空间前缀检查**:Key 不能包含系统命名空间前缀,防止绕过隔离机制
114 ///
115 /// # 性能优化
116 /// * 使用 `as_bytes().iter()` 进行字节级检查,比 `chars()` 迭代更快
117 /// * 使用 `is_ascii_alphanumeric()` 进行字符类型判断,避免 Unicode 处理开销
118 /// * 提前返回机制,一旦发现不符合规则立即返回错误
119 ///
120 /// # 安全性
121 /// * 防止通过构造特殊 Key 访问其他插件的缓存数据
122 /// * 限制字符集,防止注入攻击和特殊字符导致的 Redis 命令注入
123 ///
124 /// # Errors
125 /// * `CacheError::InvalidKey("Key 不能为空")` - Key 为空字符串
126 /// * `CacheError::InvalidKey("Key 长度不能超过 200 字符")` - Key 长度超限
127 /// * `CacheError::InvalidKey("Key 包含非法字符...")` - Key 包含不允许的字符
128 /// * `CacheError::InvalidKey("Key 不能包含命名空间前缀")` - Key 包含命名空间前缀
129 fn validate_business_key(&self, key: &str) -> Result<(), CacheError> {
130 // 1. 不能为空
131 if key.is_empty() {
132 return Err(CacheError::InvalidKey("Key 不能为空".to_string()));
133 }
134
135 // 2. 长度限制(提前检查,避免后续不必要的操作)
136 if key.len() > 200 {
137 return Err(CacheError::InvalidKey(
138 "Key 长度不能超过 200 字符".to_string(),
139 ));
140 }
141
142 // 3. 字符限制(只允许字母、数字、下划线、连字符、冒号)
143 // 性能优化:使用字节检查,比 chars() 更快
144 if !key.as_bytes().iter().all(|&b| {
145 b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b':'
146 }) {
147 return Err(CacheError::InvalidKey(
148 "Key 包含非法字符,只允许字母、数字、下划线、连字符、冒号".to_string(),
149 ));
150 }
151
152 // 4. 不能包含命名空间分隔符(防止绕过隔离)
153 // 性能优化:避免 format!,直接检查字符串片段
154 let forbidden_prefix = format!("{}:plugin:", self.manager.system_name());
155 if key.contains(&forbidden_prefix) {
156 return Err(CacheError::InvalidKey(
157 "Key 不能包含命名空间前缀".to_string(),
158 ));
159 }
160
161 Ok(())
162 }
163
164 /// 验证权限(确保只能操作自己的 Key)
165 ///
166 /// 检查给定的完整 Key 是否属于当前插件的命名空间,防止跨插件访问。
167 ///
168 /// # Arguments
169 /// * `full_key` - 完整的缓存 Key(包含命名空间前缀)
170 ///
171 /// # Returns
172 /// * `Ok(())` - Key 属于当前插件,权限验证通过
173 /// * `Err(CacheError::PermissionDenied)` - Key 不属于当前插件,权限验证失败
174 ///
175 /// # 安全性
176 /// 此方法用于防止插件通过构造 Key 访问或修改其他插件的缓存数据。
177 fn verify_permission(&self, full_key: &str) -> Result<(), CacheError> {
178 if !full_key.starts_with(&self.namespace) {
179 return Err(CacheError::PermissionDenied(
180 "Key 不属于当前插件".to_string(),
181 ));
182 }
183 Ok(())
184 }
185}
186
187#[async_trait]
188impl Cache for PluginCache {
189 /// 获取缓存值
190 ///
191 /// 根据业务 Key 获取缓存中的值,自动添加命名空间前缀。
192 /// 如果 Key 不存在或已过期,返回 `None`。
193 ///
194 /// # Arguments
195 /// * `key` - 业务 Key(不包含命名空间前缀)
196 ///
197 /// # Type Parameters
198 /// * `T` - 返回值的类型,必须实现 `DeserializeOwned` trait
199 ///
200 /// # Returns
201 /// * `Ok(Some(T))` - 成功获取到缓存值
202 /// * `Ok(None)` - Key 不存在或已过期
203 /// * `Err(CacheError)` - 操作失败(如 Key 格式错误、权限不足、反序列化失败等)
204 ///
205 /// # Errors
206 /// * `CacheError::InvalidKey` - Key 格式验证失败
207 /// * `CacheError::PermissionDenied` - Key 不属于当前插件
208 /// * `CacheError::OperationFailed` - Redis 操作失败
209 /// * `CacheError::DeserializationFailed` - JSON 反序列化失败
210 ///
211 /// # Example
212 /// ```no_run
213 /// # use secra_cache::*;
214 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
215 /// # let cache = todo!();
216 /// let value: Option<String> = cache.get("user:123").await?;
217 /// if let Some(v) = value {
218 /// println!("缓存值: {}", v);
219 /// }
220 /// # Ok(())
221 /// # }
222 /// ```
223 async fn get<T>(&self, key: &str) -> Result<Option<T>, CacheError>
224 where
225 T: DeserializeOwned,
226 {
227 let full_key = self.build_key(key)?;
228
229 // 安全优化:验证权限(防止通过构造的 key 访问其他插件的缓存)
230 self.verify_permission(&full_key)?;
231
232 let mut conn = self.manager.get_connection().await?;
233
234 // 从 Redis 获取值
235 let json_value: Option<String> = conn
236 .get(&full_key)
237 .await
238 .map_err(|e| CacheError::OperationFailed(e.to_string()))?;
239
240 // 反序列化
241 match json_value {
242 Some(json) => {
243 // 性能优化:使用 from_slice 替代 from_str(如果可能)
244 let value: T = serde_json::from_str(&json)
245 .map_err(|e| CacheError::DeserializationFailed(e.to_string()))?;
246 Ok(Some(value))
247 }
248 None => Ok(None),
249 }
250 }
251
252 /// 设置缓存值
253 ///
254 /// 将值存储到缓存中,自动添加命名空间前缀。
255 /// 如果提供了 TTL,则设置过期时间;否则使用默认 TTL。
256 /// 操作完成后会自动将 Key 添加到插件的索引中。
257 ///
258 /// # Arguments
259 /// * `key` - 业务 Key(不包含命名空间前缀)
260 /// * `value` - 要缓存的值,必须实现 `Serialize` 和 `Sync` trait
261 /// * `ttl` - 可选的过期时间,如果为 `None` 则使用默认 TTL
262 ///
263 /// # Type Parameters
264 /// * `T` - 值的类型,必须实现 `Serialize` 和 `Sync` trait
265 ///
266 /// # Returns
267 /// * `Ok(())` - 成功设置缓存
268 /// * `Err(CacheError)` - 操作失败(如 Key 格式错误、权限不足、序列化失败等)
269 ///
270 /// # Errors
271 /// * `CacheError::InvalidKey` - Key 格式验证失败
272 /// * `CacheError::PermissionDenied` - Key 不属于当前插件
273 /// * `CacheError::OperationFailed` - Redis 操作失败
274 /// * `CacheError::SerializationFailed` - JSON 序列化失败
275 ///
276 /// # 性能说明
277 /// * 索引更新是异步非阻塞的,不会影响主操作的性能
278 /// * 序列化使用 `serde_json::to_string`,会自动优化内存分配
279 ///
280 /// # Example
281 /// ```no_run
282 /// # use secra_cache::*;
283 /// # use std::time::Duration;
284 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
285 /// # let cache = todo!();
286 /// cache.set("user:123", &"John Doe", Some(Duration::from_secs(3600))).await?;
287 /// # Ok(())
288 /// # }
289 /// ```
290 async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<(), CacheError>
291 where
292 T: Serialize + Sync,
293 {
294 let full_key = self.build_key(key)?;
295
296 // 安全优化:验证权限(防止通过构造的 key 设置其他插件的缓存)
297 self.verify_permission(&full_key)?;
298
299 // 序列化为 JSON 字符串
300 // 性能优化:预分配容量(可选,serde_json 会自动优化)
301 let json_value = serde_json::to_string(value)
302 .map_err(|e| CacheError::SerializationFailed(e.to_string()))?;
303
304 let mut conn = self.manager.get_connection().await?;
305 let ttl_secs = self.manager.resolve_ttl(ttl);
306
307 // 设置到 Redis(使用 SET key value EX ttl)
308 let _: String = redis::cmd("SET")
309 .arg(&full_key)
310 .arg(&json_value)
311 .arg("EX")
312 .arg(ttl_secs)
313 .query_async(&mut conn)
314 .await
315 .map_err(|e| CacheError::OperationFailed(e.to_string()))?;
316
317 // 更新索引(异步非阻塞,不等待完成)
318 self.manager
319 .add_key_to_index(&self.plugin_id, &full_key)
320 .await;
321
322 Ok(())
323 }
324
325 /// 删除缓存值
326 ///
327 /// 从缓存中删除指定的 Key,如果 Key 存在则删除并返回 `true`,
328 /// 如果 Key 不存在则返回 `false`。
329 /// 删除成功后会自动从插件的索引中移除该 Key。
330 ///
331 /// # Arguments
332 /// * `key` - 业务 Key(不包含命名空间前缀)
333 ///
334 /// # Returns
335 /// * `Ok(true)` - Key 存在且已成功删除
336 /// * `Ok(false)` - Key 不存在
337 /// * `Err(CacheError)` - 操作失败(如 Key 格式错误、权限不足等)
338 ///
339 /// # Errors
340 /// * `CacheError::InvalidKey` - Key 格式验证失败
341 /// * `CacheError::PermissionDenied` - Key 不属于当前插件
342 /// * `CacheError::OperationFailed` - Redis 操作失败
343 ///
344 /// # Example
345 /// ```no_run
346 /// # use secra_cache::*;
347 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
348 /// # let cache = todo!();
349 /// let deleted = cache.delete("user:123").await?;
350 /// if deleted {
351 /// println!("缓存已删除");
352 /// }
353 /// # Ok(())
354 /// # }
355 /// ```
356 async fn delete(&self, key: &str) -> Result<bool, CacheError> {
357 let full_key = self.build_key(key)?;
358
359 // 验证权限
360 self.verify_permission(&full_key)?;
361
362 let mut conn = self.manager.get_connection().await?;
363
364 // 删除 Key
365 let deleted: u64 = conn
366 .del(&full_key)
367 .await
368 .map_err(|e| CacheError::OperationFailed(e.to_string()))?;
369
370 // 从索引中移除
371 if deleted > 0 {
372 self.manager
373 .remove_key_from_index(&self.plugin_id, &full_key)
374 .await;
375 }
376
377 Ok(deleted > 0)
378 }
379
380 /// 检查 Key 是否存在
381 ///
382 /// 检查指定的 Key 是否存在于缓存中,无论是否已过期。
383 ///
384 /// # Arguments
385 /// * `key` - 业务 Key(不包含命名空间前缀)
386 ///
387 /// # Returns
388 /// * `Ok(true)` - Key 存在
389 /// * `Ok(false)` - Key 不存在
390 /// * `Err(CacheError)` - 操作失败(如 Key 格式错误、权限不足等)
391 ///
392 /// # Errors
393 /// * `CacheError::InvalidKey` - Key 格式验证失败
394 /// * `CacheError::PermissionDenied` - Key 不属于当前插件
395 /// * `CacheError::OperationFailed` - Redis 操作失败
396 ///
397 /// # Note
398 /// 此方法只检查 Key 是否存在,不检查是否已过期。
399 /// 如果需要检查 Key 是否存在且未过期,建议使用 `get` 方法。
400 ///
401 /// # Example
402 /// ```no_run
403 /// # use secra_cache::*;
404 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
405 /// # let cache = todo!();
406 /// if cache.exists("user:123").await? {
407 /// println!("Key 存在");
408 /// }
409 /// # Ok(())
410 /// # }
411 /// ```
412 async fn exists(&self, key: &str) -> Result<bool, CacheError> {
413 let full_key = self.build_key(key)?;
414
415 // 安全优化:验证权限
416 self.verify_permission(&full_key)?;
417
418 let mut conn = self.manager.get_connection().await?;
419
420 // 检查 Key 是否存在
421 let exists: bool = conn
422 .exists(&full_key)
423 .await
424 .map_err(|e| CacheError::OperationFailed(e.to_string()))?;
425
426 Ok(exists)
427 }
428
429 /// 设置 Key 的过期时间
430 ///
431 /// 为已存在的 Key 设置新的过期时间。如果 Key 不存在,返回 `false`。
432 ///
433 /// # Arguments
434 /// * `key` - 业务 Key(不包含命名空间前缀)
435 /// * `ttl` - 新的过期时间
436 ///
437 /// # Returns
438 /// * `Ok(true)` - Key 存在且已成功设置过期时间
439 /// * `Ok(false)` - Key 不存在
440 /// * `Err(CacheError)` - 操作失败(如 Key 格式错误、权限不足等)
441 ///
442 /// # Errors
443 /// * `CacheError::InvalidKey` - Key 格式验证失败
444 /// * `CacheError::PermissionDenied` - Key 不属于当前插件
445 /// * `CacheError::OperationFailed` - Redis 操作失败
446 ///
447 /// # Example
448 /// ```no_run
449 /// # use secra_cache::*;
450 /// # use std::time::Duration;
451 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
452 /// # let cache = todo!();
453 /// let success = cache.expire("user:123", Duration::from_secs(7200)).await?;
454 /// if success {
455 /// println!("过期时间已更新");
456 /// }
457 /// # Ok(())
458 /// # }
459 /// ```
460 async fn expire(&self, key: &str, ttl: Duration) -> Result<bool, CacheError> {
461 let full_key = self.build_key(key)?;
462
463 // 安全优化:验证权限
464 self.verify_permission(&full_key)?;
465
466 let mut conn = self.manager.get_connection().await?;
467
468 // 设置过期时间
469 let result: i64 = redis::cmd("EXPIRE")
470 .arg(&full_key)
471 .arg(ttl.as_secs() as usize)
472 .query_async(&mut conn)
473 .await
474 .map_err(|e| CacheError::OperationFailed(e.to_string()))?;
475
476 Ok(result == 1)
477 }
478
479 /// 获取 Key 的剩余过期时间
480 ///
481 /// 返回 Key 的剩余过期时间。如果 Key 不存在、已过期或永不过期,返回 `None`。
482 ///
483 /// # Arguments
484 /// * `key` - 业务 Key(不包含命名空间前缀)
485 ///
486 /// # Returns
487 /// * `Ok(Some(Duration))` - Key 存在且有过期时间,返回剩余时间
488 /// * `Ok(None)` - Key 不存在、已过期或永不过期
489 /// * `Err(CacheError)` - 操作失败(如 Key 格式错误、权限不足等)
490 ///
491 /// # Errors
492 /// * `CacheError::InvalidKey` - Key 格式验证失败
493 /// * `CacheError::PermissionDenied` - Key 不属于当前插件
494 /// * `CacheError::OperationFailed` - Redis 操作失败
495 ///
496 /// # Redis TTL 返回值说明
497 /// * `-2` - Key 不存在,返回 `None`
498 /// * `-1` - Key 存在但永不过期,返回 `None`
499 /// * `> 0` - Key 存在且有过期时间,返回剩余秒数
500 ///
501 /// # Example
502 /// ```no_run
503 /// # use secra_cache::*;
504 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
505 /// # let cache = todo!();
506 /// if let Some(remaining) = cache.ttl("user:123").await? {
507 /// println!("剩余时间: {:?}", remaining);
508 /// } else {
509 /// println!("Key 不存在或永不过期");
510 /// }
511 /// # Ok(())
512 /// # }
513 /// ```
514 async fn ttl(&self, key: &str) -> Result<Option<Duration>, CacheError> {
515 let full_key = self.build_key(key)?;
516
517 // 安全优化:验证权限
518 self.verify_permission(&full_key)?;
519
520 let mut conn = self.manager.get_connection().await?;
521
522 // 获取剩余过期时间
523 let ttl_secs: isize = redis::cmd("TTL")
524 .arg(&full_key)
525 .query_async(&mut conn)
526 .await
527 .map_err(|e| CacheError::OperationFailed(e.to_string()))?;
528
529 match ttl_secs {
530 -2 => Ok(None), // Key 不存在
531 -1 => Ok(None), // Key 存在但永不过期
532 secs if secs > 0 => Ok(Some(Duration::from_secs(secs as u64))),
533 _ => Ok(None),
534 }
535 }
536
537 /// 清空当前插件的所有缓存
538 ///
539 /// 删除当前插件的所有缓存 Key,包括所有模块的数据。
540 /// 此操作会清空插件的索引并删除所有相关的缓存数据。
541 ///
542 /// # Returns
543 /// * `Ok(u64)` - 成功删除的 Key 数量
544 /// * `Err(CacheError)` - 操作失败
545 ///
546 /// # Errors
547 /// * `CacheError::OperationFailed` - Redis 操作失败
548 ///
549 /// # Warning
550 /// 此操作不可逆,会删除当前插件的所有缓存数据,请谨慎使用。
551 ///
552 /// # Example
553 /// ```no_run
554 /// # use secra_cache::*;
555 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
556 /// # let cache = todo!();
557 /// let deleted_count = cache.clear().await?;
558 /// println!("已删除 {} 个缓存项", deleted_count);
559 /// # Ok(())
560 /// # }
561 /// ```
562 async fn clear(&self) -> Result<u64, CacheError> {
563 // 清理当前插件的所有缓存
564 self.manager.clear_plugin(&self.plugin_id).await
565 }
566
567 /// 清空当前插件的指定模块缓存
568 ///
569 /// 删除当前插件中指定模块的所有缓存 Key。
570 /// 模块名称通常作为 Key 的前缀部分,用于逻辑分组。
571 ///
572 /// # Arguments
573 /// * `module` - 模块名称,用于标识要清理的模块
574 ///
575 /// # Returns
576 /// * `Ok(u64)` - 成功删除的 Key 数量
577 /// * `Err(CacheError)` - 操作失败
578 ///
579 /// # Errors
580 /// * `CacheError::OperationFailed` - Redis 操作失败
581 ///
582 /// # Note
583 /// 模块名称应该与 Key 的命名规范一致,通常作为 Key 的前缀使用。
584 /// 例如:如果 Key 格式为 `module:user:123`,则模块名称为 `module`。
585 ///
586 /// # Example
587 /// ```no_run
588 /// # use secra_cache::*;
589 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
590 /// # let cache = todo!();
591 /// let deleted_count = cache.clear_module("user").await?;
592 /// println!("已删除 {} 个用户模块缓存项", deleted_count);
593 /// # Ok(())
594 /// # }
595 /// ```
596 async fn clear_module(&self, module: &str) -> Result<u64, CacheError> {
597 // 清理当前插件的指定模块缓存
598 self.manager.clear_module(&self.plugin_id, module).await
599 }
600}