fbc_starter/cache/
cache_key_builder.rs

1/// Cache Key Builder Trait
2///
3/// Redis key 命名规范:
4/// 【推荐】Redis key 命名需具有可读性以及可管理性,不该使用含义不清的 key 以及特别长的 key 名;
5/// 【强制】以英文字母开头,命名中只能出现小写字母、数字、英文点号(.)和英文半角冒号(:);
6/// 【强制】不要包含特殊字符,如下划线、空格、换行、单双引号以及其他转义字符;
7///
8/// 命名规范:
9/// 【强制】命名规范:[前缀:][租户编码:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
10///
11/// 0)前缀:   可选。用来区分不同项目,不同环境。
12/// 1)租户ID: 可选。用来区分不同租户数据缓存。
13/// 2)服务模块名:可选。用来区分不同服务或功能模块的缓存。
14/// 3)业务类型: 必填。用来区分不同业务类型的数据缓存。通常设置为表名。
15/// 4)业务字段: 可选。用来区分业务值是哪个字段。通常设置为字段名。
16/// 5)value类型:可选。用来区分 value 类型。
17/// 6)业务值:   可选。用来区分同一业务类型的不同行的数据缓存。
18///
19/// 示例:
20/// ```text
21/// 0000:authority:user.activity:id.id:1:3:number => [1,2,3,4]
22/// 表示:租户(0000),权限服务(authority)中用户表(user)的用户id为1,活动表(activity)的活动id为3
23///
24/// 0000:authority:user:id:1:obj => {"id":1, "name":"张三"}
25/// 表示:租户(0000),权限服务(authority)中用户表(user)的用户id为1的数据
26/// ```
27use super::cache_key::{CacheHashKey, CacheKey, ValueType};
28use std::time::Duration;
29
30pub trait CacheKeyBuilder {
31    /// 缓存前缀,用于区分项目、环境等
32    fn get_prefix(&self) -> Option<&str> {
33        None
34    }
35
36    /// 租户 ID,用于区分租户
37    /// 非租户模式返回 None
38    fn get_tenant(&self) -> Option<&str> {
39        None
40    }
41
42    /// 设置租户 ID(默认不做处理,子类可重写)
43    fn set_tenant_id(&mut self, _tenant_id: u64) {}
44
45    /// 服务模块名,用于区分后端服务、前端模块等
46    fn get_modular(&self) -> Option<&str> {
47        None
48    }
49
50    /// key 的业务类型,用于区分表(必填)
51    fn get_table(&self) -> &str;
52
53    /// key 的字段名,用于区分字段
54    fn get_field(&self) -> Option<&str> {
55        None
56    }
57
58    /// 缓存的 value 存储的类型
59    fn get_value_type(&self) -> ValueType {
60        ValueType::Obj
61    }
62
63    /// 缓存自动过期时间
64    fn get_expire(&self) -> Option<Duration> {
65        None
66    }
67
68    /// 获取通配符模式
69    fn get_pattern(&self) -> String {
70        format!("*:{}:*", self.get_table())
71    }
72
73    /// 构建通用 KV 模式的 cache key
74    /// 兼容 redis 和 caffeine
75    ///
76    /// # 参数
77    /// - `uniques`: 动态参数(业务值)
78    fn key(&self, uniques: &[&dyn ToString]) -> CacheKey {
79        let key = self.build_key(uniques);
80        assert!(!key.is_empty(), "key 不能为空");
81        CacheKey::new(key, self.get_expire())
82    }
83
84    /// 构建 Redis 类型的 hash cache key(带 field)
85    ///
86    /// # 参数
87    /// - `field`: hash field
88    /// - `uniques`: 动态参数
89    fn hash_field_key(&self, field: &dyn ToString, uniques: &[&dyn ToString]) -> CacheHashKey {
90        let key = self.build_key(uniques);
91        assert!(!key.is_empty(), "key 不能为空");
92        CacheHashKey::new(key, Some(field.to_string()), self.get_expire())
93    }
94
95    /// 构建 Redis 类型的 hash cache key(无 field)
96    ///
97    /// # 参数
98    /// - `uniques`: 动态参数
99    fn hash_key(&self, uniques: &[&dyn ToString]) -> CacheHashKey {
100        let key = self.build_key(uniques);
101        assert!(!key.is_empty(), "key 不能为空");
102        CacheHashKey::new(key, None, self.get_expire())
103    }
104
105    /// 根据动态参数拼接 key
106    ///
107    /// key 命名规范:[前缀:][租户ID:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
108    fn build_key(&self, uniques: &[&dyn ToString]) -> String {
109        let mut parts = Vec::new();
110
111        // 前缀
112        if let Some(prefix) = self.get_prefix() {
113            if !prefix.is_empty() {
114                parts.push(prefix.to_string());
115            }
116        }
117
118        // 租户编码
119        if let Some(tenant) = self.get_tenant() {
120            if !tenant.is_empty() {
121                parts.push(tenant.to_string());
122            }
123        }
124
125        // 服务模块名
126        if let Some(modular) = self.get_modular() {
127            if !modular.is_empty() {
128                parts.push(modular.to_string());
129            }
130        }
131
132        // 业务类型(必填)
133        let table = self.get_table();
134        assert!(!table.is_empty(), "缓存业务类型不能为空");
135        parts.push(table.to_string());
136
137        // 业务字段
138        if let Some(field) = self.get_field() {
139            if !field.is_empty() {
140                parts.push(field.to_string());
141            }
142        }
143
144        // value 类型
145        parts.push(self.get_value_type().as_str().to_string());
146
147        // 业务值
148        for unique in uniques {
149            let value = unique.to_string();
150            if !value.is_empty() {
151                parts.push(value);
152            }
153        }
154
155        parts.join(":")
156    }
157}
158
159/// 简单的缓存键构建器实现
160///
161/// 用于快速构建缓存键,无需实现完整的 trait
162#[derive(Debug, Clone)]
163pub struct SimpleCacheKeyBuilder {
164    pub prefix: Option<String>,
165    pub tenant: Option<String>,
166    pub modular: Option<String>,
167    pub table: String,
168    pub field: Option<String>,
169    pub value_type: ValueType,
170    pub expire: Option<Duration>,
171}
172
173impl SimpleCacheKeyBuilder {
174    /// 创建新的缓存键构建器
175    pub fn new(table: impl Into<String>) -> Self {
176        Self {
177            prefix: None,
178            tenant: None,
179            modular: None,
180            table: table.into(),
181            field: None,
182            value_type: ValueType::Obj,
183            expire: None,
184        }
185    }
186
187    /// 设置前缀
188    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
189        self.prefix = Some(prefix.into());
190        self
191    }
192
193    /// 设置租户
194    pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
195        self.tenant = Some(tenant.into());
196        self
197    }
198
199    /// 设置模块名
200    pub fn with_modular(mut self, modular: impl Into<String>) -> Self {
201        self.modular = Some(modular.into());
202        self
203    }
204
205    /// 设置字段名
206    pub fn with_field(mut self, field: impl Into<String>) -> Self {
207        self.field = Some(field.into());
208        self
209    }
210
211    /// 设置 value 类型
212    pub fn with_value_type(mut self, value_type: ValueType) -> Self {
213        self.value_type = value_type;
214        self
215    }
216
217    /// 设置过期时间
218    pub fn with_expire(mut self, expire: Duration) -> Self {
219        self.expire = Some(expire);
220        self
221    }
222}
223
224impl CacheKeyBuilder for SimpleCacheKeyBuilder {
225    fn get_prefix(&self) -> Option<&str> {
226        self.prefix.as_deref()
227    }
228
229    fn get_tenant(&self) -> Option<&str> {
230        self.tenant.as_deref()
231    }
232
233    fn get_modular(&self) -> Option<&str> {
234        self.modular.as_deref()
235    }
236
237    fn get_table(&self) -> &str {
238        &self.table
239    }
240
241    fn get_field(&self) -> Option<&str> {
242        self.field.as_deref()
243    }
244
245    fn get_value_type(&self) -> ValueType {
246        self.value_type
247    }
248
249    fn get_expire(&self) -> Option<Duration> {
250        self.expire
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_simple_cache_key_builder() {
260        let builder = SimpleCacheKeyBuilder::new("user")
261            .with_prefix("dev")
262            .with_tenant("0000")
263            .with_modular("authority")
264            .with_field("id")
265            .with_value_type(ValueType::Obj);
266
267        let key = builder.key(&[&1u64]);
268        assert_eq!(key.key, "dev:0000:authority:user:id:obj:1");
269    }
270
271    #[test]
272    fn test_cache_key_without_optional_fields() {
273        let builder = SimpleCacheKeyBuilder::new("tenant");
274
275        let key = builder.key(&[&1u64]);
276        assert_eq!(key.key, "tenant:obj:1");
277    }
278
279    #[test]
280    fn test_hash_key() {
281        let builder = SimpleCacheKeyBuilder::new("user")
282            .with_tenant("0000")
283            .with_modular("authority");
284
285        let hash_key = builder.hash_field_key(&"name", &[&1u64]);
286        assert_eq!(hash_key.key, "0000:authority:user:obj:1");
287        assert_eq!(hash_key.field, Some("name".to_string()));
288    }
289
290    #[test]
291    fn test_multiple_uniques() {
292        let builder = SimpleCacheKeyBuilder::new("user.activity")
293            .with_tenant("0000")
294            .with_modular("authority")
295            .with_field("id.id")
296            .with_value_type(ValueType::Number);
297
298        let key = builder.key(&[&1u64, &3u64]);
299        assert_eq!(key.key, "0000:authority:user.activity:id.id:number:1:3");
300    }
301}