Skip to main content

sa_token_adapter/
storage.rs

1// Author: 金书记
2//
3//! 存储适配器 trait 定义
4
5use async_trait::async_trait;
6use std::time::Duration;
7use thiserror::Error;
8
9/// Storage operation result | 存储操作结果
10pub type StorageResult<T> = Result<T, StorageError>;
11
12/// Storage backend error | 存储后端错误
13#[derive(Debug, Error)]
14pub enum StorageError {
15    /// Generic storage operation failure | 通用存储操作失败
16    #[error("Storage operation failed: {0}")]
17    OperationFailed(String),
18
19    /// Key does not exist | 键不存在
20    #[error("Key not found: {0}")]
21    KeyNotFound(String),
22
23    /// Value serialize/deserialize failed | 值序列化或反序列化失败
24    #[error("Serialization error: {0}")]
25    SerializationError(String),
26
27    /// Backend connection failure | 后端连接失败
28    #[error("Connection error: {0}")]
29    ConnectionError(String),
30
31    /// Unexpected storage internal error | 未预期的存储内部错误
32    #[error("Internal error: {0}")]
33    InternalError(String),
34
35    /// 当前存储后端尚未实现该操作(A1-8:携带操作名称,便于调试)
36    #[error("Unsupported operation '{0}' on this storage backend")]
37    Unsupported(&'static str),
38}
39
40/// 游标扫描的一页结果
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct ScanPage {
43    /// 本页命中的逻辑键(不含 Redis 物理前缀)
44    pub keys: Vec<String>,
45    /// 下一页游标;为 0 表示遍历结束
46    pub next_cursor: u64,
47}
48
49/// 存储适配器 trait
50///
51/// ## 键契约(A3)| Key Contract (A3)
52///
53/// - 所有方法的 `key` 参数均为 **逻辑键**(由 `SaKeys` 构造);实现层不得再叠加第二层应用前缀,
54///   除非显式配置为物理租户分区前缀(如 Redis `key_prefix`)。
55/// - All `key` arguments are **logical keys** from `SaKeys`; implementations must not prepend
56///   another application prefix unless configured as an optional physical partition.
57///
58/// 0.2.0 破坏性变更:删除 `keys()` 默认桩,统一改用 `scan` 分页扫描。
59#[async_trait]
60pub trait SaStorage: Send + Sync {
61    /// Read value by key | 按键读取值
62    async fn get(&self, key: &str) -> StorageResult<Option<String>>;
63    /// Write value with optional TTL | 写入值(可选 TTL)
64    async fn set(&self, key: &str, value: &str, ttl: Option<Duration>) -> StorageResult<()>;
65    /// Delete a key | 删除键
66    async fn delete(&self, key: &str) -> StorageResult<()>;
67    /// Whether the key exists | 键是否存在
68    async fn exists(&self, key: &str) -> StorageResult<bool>;
69    /// Update key TTL | 更新键过期时间
70    async fn expire(&self, key: &str, ttl: Duration) -> StorageResult<()>;
71    /// Remaining TTL, if any | 剩余过期时间(若有)
72    async fn ttl(&self, key: &str) -> StorageResult<Option<Duration>>;
73
74    /// 批量读:默认逐键 get,具体后端可覆盖为 mget
75    async fn mget(&self, keys: &[&str]) -> StorageResult<Vec<Option<String>>> {
76        let mut results = Vec::with_capacity(keys.len());
77        for key in keys {
78            results.push(self.get(key).await?);
79        }
80        Ok(results)
81    }
82
83    /// 批量写:默认逐键 set
84    async fn mset(&self, items: &[(&str, &str)], ttl: Option<Duration>) -> StorageResult<()> {
85        for (key, value) in items {
86            self.set(key, value, ttl).await?;
87        }
88        Ok(())
89    }
90
91    /// 批量删:默认逐键 delete
92    async fn mdel(&self, keys: &[&str]) -> StorageResult<()> {
93        for key in keys {
94            self.delete(key).await?;
95        }
96        Ok(())
97    }
98
99    /// 自增:**默认实现非原子**(读-改-写),并发环境下计数不准确
100    ///
101    /// 【A1-1 警告】MemoryStorage / RedisStorage 应覆盖此方法,提供原子递增语义。
102    async fn incr(&self, key: &str) -> StorageResult<i64> {
103        let current = self
104            .get(key)
105            .await?
106            .and_then(|v| v.parse::<i64>().ok())
107            .unwrap_or(0);
108        let new_value = current + 1;
109        self.set(key, &new_value.to_string(), None).await?;
110        Ok(new_value)
111    }
112
113    /// 自减:**默认实现非原子**(读-改-写),语义同 incr
114    ///
115    /// 【A1-1 警告】MemoryStorage / RedisStorage 应覆盖此方法。
116    async fn decr(&self, key: &str) -> StorageResult<i64> {
117        let current = self
118            .get(key)
119            .await?
120            .and_then(|v| v.parse::<i64>().ok())
121            .unwrap_or(0);
122        let new_value = current - 1;
123        self.set(key, &new_value.to_string(), None).await?;
124        Ok(new_value)
125    }
126
127    /// Remove all keys | 清空全部键
128    async fn clear(&self) -> StorageResult<()>;
129
130    /// 仅当键不存在(或已过期)时写入;成功占位返回 true
131    ///
132    /// 用途:nonce 占位、分布式锁
133    async fn set_if_absent(
134        &self,
135        key: &str,
136        value: &str,
137        ttl: Option<Duration>,
138    ) -> StorageResult<bool>;
139
140    /// 读取并删除;保证单次消费者语义
141    ///
142    /// 用途:nonce 一次性消费、ticket 消费
143    async fn get_del(&self, key: &str) -> StorageResult<Option<String>>;
144
145    /// 比较并交换:当且仅当当前值等于 `expected` 时,写入 `new_value`
146    ///
147    /// 【A1-7 语义明确】:
148    /// - `expected = None`:期望键**不存在或已过期**(首次写入语义)
149    /// - `expected = Some("old")`:期望键当前值为 `"old"`(乐观更新语义)
150    ///
151    /// 返回 `true` 表示 CAS 成功(值已更新),`false` 表示期望不匹配(值未修改)
152    ///
153    /// 用途:token 索引 / 权限列表的 JSON 乐观更新
154    async fn compare_and_swap(
155        &self,
156        key: &str,
157        expected: Option<&str>,
158        new_value: &str,
159        ttl: Option<Duration>,
160    ) -> StorageResult<bool>;
161
162    /// 比较并删除:仅当当前值等于 expected 时删除
163    ///
164    /// 返回 `true` 表示删除成功,`false` 表示期望不匹配(键未删除)
165    async fn compare_and_delete(&self, key: &str, expected: &str) -> StorageResult<bool>;
166
167    /// 向集合追加成员;unique=true 时跳过重复项
168    ///
169    /// 【A1-2】RedisStorage 使用 Lua 原子去重版本,保证 unique 并发安全。
170    ///
171    /// 用途:多设备 token 索引、权限列表
172    async fn list_push(
173        &self,
174        key: &str,
175        member: &str,
176        unique: bool,
177        ttl: Option<Duration>,
178    ) -> StorageResult<usize>;
179
180    /// 从集合移除成员;返回是否确实移除
181    async fn list_remove(&self, key: &str, member: &str) -> StorageResult<bool>;
182
183    /// 分页读取集合成员
184    ///
185    /// 【A1-5 注意】MemoryStorage 在键过期后返回空列表(不返回过期数据)
186    async fn list_range(
187        &self,
188        key: &str,
189        start: usize,
190        limit: Option<usize>,
191    ) -> StorageResult<Vec<String>>;
192
193    /// 集合长度
194    ///
195    /// 【A1-5 注意】MemoryStorage 在键过期后返回 0
196    async fn list_len(&self, key: &str) -> StorageResult<usize>;
197
198    /// 游标扫描:pattern 为 glob(`*` / `?`),cursor 为上一页 next_cursor
199    ///
200    /// - `pattern` 为逻辑键 glob(由 `SaKeys::scan_pattern` 等构造)
201    /// - 返回的 `ScanPage::keys` 均为逻辑键(Redis 实现会剥离物理前缀)
202    /// - `limit` 为**每页建议条数**;Redis SCAN 的 COUNT 仅为提示,实现可返回更多匹配项
203    /// - 仅扫描标量键命名空间;列表键(`list:` 前缀)会被排除
204    ///
205    /// 【A1-9 一致性保证差异】:
206    /// - **MemoryStorage**:强一致性,单次 scan 内不会返回重复键,并发插入可能跳过新键
207    /// - **RedisStorage**:弱一致性(Redis SCAN 语义),可能返回重复键、遗漏或额外包含并发修改的键
208    ///
209    /// 【A1-3/A1-4 性能与并发】:
210    /// - MemoryStorage 每次全量排序,10w+ 键时延迟高(仅开发/测试场景)
211    /// - MemoryStorage 并发修改时游标可能跳过或重复(文档标注非生产)
212    ///
213    /// 用途:logout_by_login_id 回退扫描、批量过期键清理
214    async fn scan(&self, pattern: &str, cursor: u64, limit: usize) -> StorageResult<ScanPage>;
215}
216
217/// 分页扫描直到结束,聚合全部匹配键
218///
219/// 【A1-9 弱一致性处理】:RedisStorage 可能返回重复键,此函数**不自动去重**。
220/// 如需去重,调用 [`scan_all_keys_dedup`]。
221pub async fn scan_all_keys(
222    storage: &dyn SaStorage,
223    pattern: &str,
224    page_size: usize,
225) -> StorageResult<Vec<String>> {
226    let mut cursor = 0u64;
227    let mut all = Vec::new();
228    loop {
229        let page = storage.scan(pattern, cursor, page_size).await?;
230        all.extend(page.keys);
231        if page.next_cursor == 0 {
232            break;
233        }
234        cursor = page.next_cursor;
235    }
236    Ok(all)
237}
238
239/// 分页扫描直到结束,聚合全部匹配键并去重
240///
241/// 【A1-9 补充】:应对 Redis SCAN 的弱一致性,自动去重。
242pub async fn scan_all_keys_dedup(
243    storage: &dyn SaStorage,
244    pattern: &str,
245    page_size: usize,
246) -> StorageResult<Vec<String>> {
247    use std::collections::HashSet;
248    let all = scan_all_keys(storage, pattern, page_size).await?;
249    let deduped: Vec<String> = all
250        .into_iter()
251        .collect::<HashSet<_>>()
252        .into_iter()
253        .collect();
254    Ok(deduped)
255}