ai-engineering-init 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/.claude/hooks/skill-forced-eval.js +4 -1
  2. package/.claude/settings.json +3 -3
  3. package/.claude/skills/add-skill/SKILL.md +252 -116
  4. package/.claude/skills/api-development/SKILL.md +83 -377
  5. package/.claude/skills/architecture-design/SKILL.md +138 -632
  6. package/.claude/skills/backend-annotations/SKILL.md +134 -506
  7. package/.claude/skills/banana-image/SKILL.md +10 -3
  8. package/.claude/skills/brainstorm/SKILL.md +103 -535
  9. package/.claude/skills/bug-detective/SKILL.md +147 -1097
  10. package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
  11. package/.claude/skills/code-patterns/SKILL.md +116 -426
  12. package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  13. package/.claude/skills/crud-development/SKILL.md +64 -304
  14. package/.claude/skills/data-permission/SKILL.md +105 -412
  15. package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
  16. package/.claude/skills/file-oss-management/SKILL.md +106 -714
  17. package/.claude/skills/file-oss-management/references/entities.md +105 -0
  18. package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
  19. package/.claude/skills/leniu-api-development/SKILL.md +142 -626
  20. package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
  21. package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
  22. package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
  23. package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
  24. package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  25. package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
  26. package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
  27. package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
  28. package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
  29. package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
  30. package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
  31. package/.claude/skills/leniu-java-export/SKILL.md +94 -379
  32. package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
  33. package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
  34. package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  35. package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
  36. package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  37. package/.claude/skills/leniu-report-customization/SKILL.md +111 -325
  38. package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
  39. package/.claude/skills/leniu-report-standard-customization/SKILL.md +328 -0
  40. package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  41. package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  42. package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
  43. package/.claude/skills/mysql-debug/SKILL.md +364 -0
  44. package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
  45. package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
  46. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  47. package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
  48. package/.claude/skills/openspec-explore/SKILL.md +10 -1
  49. package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
  50. package/.claude/skills/openspec-new-change/SKILL.md +9 -1
  51. package/.claude/skills/openspec-onboard/SKILL.md +15 -130
  52. package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
  53. package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
  54. package/.claude/skills/performance-doctor/SKILL.md +110 -434
  55. package/.claude/skills/redis-cache/SKILL.md +89 -595
  56. package/.claude/skills/redis-cache/references/listeners.md +23 -0
  57. package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
  58. package/.claude/skills/security-guard/SKILL.md +137 -532
  59. package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
  60. package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
  61. package/.claude/skills/sms-mail/SKILL.md +116 -574
  62. package/.claude/skills/sms-mail/references/mail-config.md +88 -0
  63. package/.claude/skills/sms-mail/references/sms-config.md +74 -0
  64. package/.claude/skills/social-login/SKILL.md +112 -514
  65. package/.claude/skills/social-login/references/provider-configs.md +118 -0
  66. package/.claude/skills/tenant-management/SKILL.md +129 -444
  67. package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
  68. package/.claude/skills/test-development/SKILL.md +86 -540
  69. package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
  70. package/.claude/skills/utils-toolkit/SKILL.md +52 -305
  71. package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  72. package/.claude/skills/websocket-sse/SKILL.md +105 -550
  73. package/.claude/skills/workflow-engine/SKILL.md +147 -502
  74. package/.codex/skills/add-skill/SKILL.md +252 -116
  75. package/.codex/skills/api-development/SKILL.md +172 -599
  76. package/.codex/skills/architecture-design/SKILL.md +138 -504
  77. package/.codex/skills/backend-annotations/SKILL.md +134 -496
  78. package/.codex/skills/banana-image/SKILL.md +10 -3
  79. package/.codex/skills/brainstorm/SKILL.md +103 -535
  80. package/.codex/skills/bug-detective/SKILL.md +147 -1097
  81. package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
  82. package/.codex/skills/code-patterns/SKILL.md +120 -282
  83. package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  84. package/.codex/skills/crud-development/SKILL.md +64 -292
  85. package/.codex/skills/data-permission/SKILL.md +108 -407
  86. package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
  87. package/.codex/skills/database-ops/SKILL.md +8 -154
  88. package/.codex/skills/error-handler/SKILL.md +10 -0
  89. package/.codex/skills/file-oss-management/SKILL.md +106 -714
  90. package/.codex/skills/file-oss-management/references/entities.md +105 -0
  91. package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
  92. package/.codex/skills/git-workflow/SKILL.md +27 -5
  93. package/.codex/skills/leniu-api-development/SKILL.md +142 -626
  94. package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
  95. package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
  96. package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
  97. package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
  98. package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  99. package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
  100. package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
  101. package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
  102. package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
  103. package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
  104. package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
  105. package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
  106. package/.codex/skills/leniu-java-export/SKILL.md +94 -379
  107. package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
  108. package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
  109. package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  110. package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
  111. package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  112. package/.codex/skills/leniu-report-customization/SKILL.md +111 -325
  113. package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
  114. package/.codex/skills/leniu-report-standard-customization/SKILL.md +328 -0
  115. package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  116. package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  117. package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
  118. package/.codex/skills/mysql-debug/SKILL.md +364 -0
  119. package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
  120. package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
  121. package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  122. package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
  123. package/.codex/skills/openspec-explore/SKILL.md +10 -1
  124. package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
  125. package/.codex/skills/openspec-new-change/SKILL.md +9 -1
  126. package/.codex/skills/openspec-onboard/SKILL.md +15 -130
  127. package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
  128. package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
  129. package/.codex/skills/performance-doctor/SKILL.md +110 -434
  130. package/.codex/skills/project-navigator/SKILL.md +20 -1
  131. package/.codex/skills/redis-cache/SKILL.md +93 -589
  132. package/.codex/skills/redis-cache/references/listeners.md +23 -0
  133. package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
  134. package/.codex/skills/security-guard/SKILL.md +141 -527
  135. package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
  136. package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
  137. package/.codex/skills/sms-mail/SKILL.md +116 -574
  138. package/.codex/skills/sms-mail/references/mail-config.md +88 -0
  139. package/.codex/skills/sms-mail/references/sms-config.md +74 -0
  140. package/.codex/skills/social-login/SKILL.md +112 -514
  141. package/.codex/skills/social-login/references/provider-configs.md +118 -0
  142. package/.codex/skills/store-pc/SKILL.md +258 -383
  143. package/.codex/skills/tenant-management/SKILL.md +129 -444
  144. package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
  145. package/.codex/skills/test-development/SKILL.md +86 -540
  146. package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
  147. package/.codex/skills/ui-pc/SKILL.md +350 -387
  148. package/.codex/skills/utils-toolkit/SKILL.md +52 -283
  149. package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  150. package/.codex/skills/websocket-sse/SKILL.md +105 -550
  151. package/.codex/skills/workflow-engine/SKILL.md +147 -502
  152. package/.cursor/hooks.json +3 -3
  153. package/.cursor/rules/skill-activation.mdc +2 -0
  154. package/.cursor/skills/add-skill/SKILL.md +252 -116
  155. package/.cursor/skills/api-development/SKILL.md +83 -377
  156. package/.cursor/skills/architecture-design/SKILL.md +138 -632
  157. package/.cursor/skills/backend-annotations/SKILL.md +134 -506
  158. package/.cursor/skills/banana-image/SKILL.md +10 -3
  159. package/.cursor/skills/brainstorm/SKILL.md +103 -535
  160. package/.cursor/skills/bug-detective/SKILL.md +147 -1097
  161. package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
  162. package/.cursor/skills/code-patterns/SKILL.md +116 -426
  163. package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  164. package/.cursor/skills/crud-development/SKILL.md +64 -304
  165. package/.cursor/skills/data-permission/SKILL.md +105 -412
  166. package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
  167. package/.cursor/skills/file-oss-management/SKILL.md +106 -714
  168. package/.cursor/skills/file-oss-management/references/entities.md +105 -0
  169. package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
  170. package/.cursor/skills/git-workflow/SKILL.md +27 -5
  171. package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
  172. package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
  173. package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
  174. package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
  175. package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
  176. package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  177. package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
  178. package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
  179. package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
  180. package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
  181. package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
  182. package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
  183. package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
  184. package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
  185. package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
  186. package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
  187. package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  188. package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
  189. package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  190. package/.cursor/skills/leniu-report-customization/SKILL.md +111 -325
  191. package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
  192. package/.cursor/skills/leniu-report-standard-customization/SKILL.md +328 -0
  193. package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  194. package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  195. package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
  196. package/.cursor/skills/mysql-debug/SKILL.md +364 -0
  197. package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
  198. package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
  199. package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  200. package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
  201. package/.cursor/skills/openspec-explore/SKILL.md +10 -1
  202. package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
  203. package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
  204. package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
  205. package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
  206. package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
  207. package/.cursor/skills/performance-doctor/SKILL.md +110 -434
  208. package/.cursor/skills/redis-cache/SKILL.md +89 -595
  209. package/.cursor/skills/redis-cache/references/listeners.md +23 -0
  210. package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
  211. package/.cursor/skills/security-guard/SKILL.md +137 -532
  212. package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
  213. package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
  214. package/.cursor/skills/sms-mail/SKILL.md +116 -574
  215. package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
  216. package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
  217. package/.cursor/skills/social-login/SKILL.md +112 -514
  218. package/.cursor/skills/social-login/references/provider-configs.md +118 -0
  219. package/.cursor/skills/tenant-management/SKILL.md +129 -444
  220. package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
  221. package/.cursor/skills/test-development/SKILL.md +86 -540
  222. package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
  223. package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
  224. package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  225. package/.cursor/skills/websocket-sse/SKILL.md +105 -550
  226. package/.cursor/skills/workflow-engine/SKILL.md +147 -502
  227. package/AGENTS.md +1 -0
  228. package/package.json +1 -1
@@ -9,16 +9,14 @@ description: |
9
9
  - 实现分布式锁
10
10
  - 实现接口限流
11
11
  - Redis发布订阅
12
- - 缓存穿透/雪崩/击穿问题
13
12
  - 缓存key设计和命名
14
- - 缓存过期时间设置
15
13
  - 缓存清理和刷新
16
14
 
17
- 触发词:Redis、缓存、Cache、@Cacheable、@CacheEvict、@CachePut、RedisUtils、CacheUtils、分布式锁、RLock、限流、RateLimiter、发布订阅、缓存穿透、缓存雪崩、缓存击穿、缓存key、缓存过期、缓存清理
15
+ 触发词:Redis、缓存、Cache、@Cacheable、@CacheEvict、@CachePut、RedisUtils、CacheUtils、分布式锁、RLock、限流、RateLimiter、发布订阅、缓存key、缓存过期
18
16
 
19
17
  核心警告:
20
18
  - @Cacheable返回值不能使用不可变集合(List.of()、Set.of()、Map.of())
21
- - 分布式锁必须在finally中释放
19
+ - 分布式锁必须在finally中释放,且需注入RedissonClient(RedisUtils无getLock)
22
20
  - keys()和deleteKeys()会忽略租户隔离
23
21
  ---
24
22
 
@@ -34,7 +32,7 @@ description: |
34
32
  | 集合缓存 | `RedisUtils.setCacheList/Set/Map()` | List/Set/Map 操作 |
35
33
  | Spring Cache | `CacheUtils.get/put/evict()` | Spring Cache 封装 |
36
34
  | 缓存注解 | `@Cacheable/@CachePut/@CacheEvict` | 声明式缓存 |
37
- | 分布式锁 | Redisson 原生 | 需自行注入 RedissonClient |
35
+ | 分布式锁 | `redissonClient.getLock()` | 需注入 RedissonClient |
38
36
  | 限流控制 | `RedisUtils.rateLimiter()` | 基于 Redisson |
39
37
  | 原子操作 | `RedisUtils.incrAtomicValue()` | 原子递增/递减 |
40
38
  | 发布订阅 | `RedisUtils.publish/subscribe()` | 消息通信 |
@@ -50,126 +48,51 @@ import org.dromara.common.redis.utils.RedisUtils;
50
48
  ### 1.1 基础缓存操作
51
49
 
52
50
  ```java
53
- // 永不过期
54
- RedisUtils.setCacheObject("user:123", userObj);
51
+ RedisUtils.setCacheObject("user:123", userObj); // 永不过期
52
+ RedisUtils.setCacheObject("user:123", userObj, Duration.ofMinutes(30)); // 带过期时间
53
+ RedisUtils.setCacheObject("user:123", userObj, true); // 保留原有TTL(Redis 6.0+)
54
+ boolean ok = RedisUtils.setObjectIfAbsent("user:123", userObj, Duration.ofMinutes(30)); // 不存在时设置
55
+ boolean ok = RedisUtils.setObjectIfExists("user:123", userObj, Duration.ofMinutes(30)); // 存在时设置
55
56
 
56
- // 设置过期时间
57
- RedisUtils.setCacheObject("user:123", userObj, Duration.ofMinutes(30));
58
-
59
- // 保留原有TTL(需要Redis 6.0+)
60
- RedisUtils.setCacheObject("user:123", userObj, true);
61
-
62
- // 仅当key不存在时设置
63
- boolean success = RedisUtils.setObjectIfAbsent("user:123", userObj, Duration.ofMinutes(30));
64
-
65
- // 仅当key存在时设置
66
- boolean success = RedisUtils.setObjectIfExists("user:123", userObj, Duration.ofMinutes(30));
67
-
68
- // 获取缓存
69
- User user = RedisUtils.getCacheObject("user:123");
70
-
71
- // 获取剩余存活时间(毫秒)
72
- long ttl = RedisUtils.getTimeToLive("user:123");
73
- // 返回值:-1表示永不过期,-2表示key不存在
74
-
75
- // 删除单个缓存
76
- boolean deleted = RedisUtils.deleteObject("user:123");
77
-
78
- // 批量删除
79
- RedisUtils.deleteObject(Arrays.asList("user:123", "user:456"));
80
-
81
- // 检查缓存是否存在
82
- boolean exists = RedisUtils.isExistsObject("user:123");
83
-
84
- // 设置过期时间
85
- RedisUtils.expire("user:123", Duration.ofMinutes(30));
57
+ User user = RedisUtils.getCacheObject("user:123"); // 获取
58
+ long ttl = RedisUtils.getTimeToLive("user:123"); // TTL毫秒(-1永不过期,-2不存在)
59
+ boolean deleted = RedisUtils.deleteObject("user:123"); // 删除
60
+ RedisUtils.deleteObject(Arrays.asList("user:123", "user:456")); // 批量删除
61
+ boolean exists = RedisUtils.isExistsObject("user:123"); // 是否存在
62
+ RedisUtils.expire("user:123", Duration.ofMinutes(30)); // 设置过期
86
63
  ```
87
64
 
88
65
  ### 1.2 集合操作
89
66
 
90
- #### List 操作
91
-
92
67
  ```java
93
- // 缓存List数据
94
- List<String> dataList = new ArrayList<>();
95
- dataList.add("item1");
96
- dataList.add("item2");
68
+ // List
97
69
  RedisUtils.setCacheList("myList", dataList);
98
-
99
- // 向List追加单个数据
100
70
  RedisUtils.addCacheList("myList", "item3");
101
-
102
- // 获取完整List
103
71
  List<String> result = RedisUtils.getCacheList("myList");
104
-
105
- // 获取指定范围数据
106
72
  List<String> range = RedisUtils.getCacheListRange("myList", 0, 10);
107
- ```
108
73
 
109
- #### Set 操作
110
-
111
- ```java
112
- // 缓存Set数据
113
- Set<String> dataSet = new HashSet<>();
114
- dataSet.add("value1");
115
- dataSet.add("value2");
74
+ // Set
116
75
  RedisUtils.setCacheSet("mySet", dataSet);
117
-
118
- // 向Set追加单个数据
119
76
  boolean added = RedisUtils.addCacheSet("mySet", "value3");
120
-
121
- // 获取Set数据
122
77
  Set<String> result = RedisUtils.getCacheSet("mySet");
123
- ```
124
-
125
- #### Map 操作
126
78
 
127
- ```java
128
- // 缓存Map数据
129
- Map<String, Object> dataMap = new HashMap<>();
130
- dataMap.put("key1", "value1");
131
- dataMap.put("key2", "value2");
79
+ // Map
132
80
  RedisUtils.setCacheMap("myMap", dataMap);
133
-
134
- // 设置Map中的单个值
135
81
  RedisUtils.setCacheMapValue("myMap", "key3", "value3");
136
-
137
- // 获取Map中的单个值
138
82
  String value = RedisUtils.getCacheMapValue("myMap", "key1");
139
-
140
- // 获取完整Map
141
83
  Map<String, Object> result = RedisUtils.getCacheMap("myMap");
142
-
143
- // 获取Map的所有key
144
84
  Set<String> keys = RedisUtils.getCacheMapKeySet("myMap");
145
-
146
- // 删除Map中的单个值
147
- Object deleted = RedisUtils.delCacheMapValue("myMap", "key1");
148
-
149
- // 批量删除Map中的多个值
85
+ RedisUtils.delCacheMapValue("myMap", "key1");
150
86
  RedisUtils.delMultiCacheMapValue("myMap", new HashSet<>(Arrays.asList("key1", "key2")));
151
-
152
- // 获取Map中的多个值
153
- Map<String, Object> values = RedisUtils.getMultiCacheMapValue("myMap",
154
- new HashSet<>(Arrays.asList("key1", "key2")));
87
+ Map<String, Object> values = RedisUtils.getMultiCacheMapValue("myMap", keySet);
155
88
  ```
156
89
 
157
90
  ### 1.3 发布订阅
158
91
 
159
92
  ```java
160
- // 发布消息到指定频道
161
93
  RedisUtils.publish("notification:channel", messageObj);
162
-
163
- // 发布消息并自定义处理
164
- RedisUtils.publish("notification:channel", messageObj, msg -> {
165
- log.info("消息已发布: {}", msg);
166
- });
167
-
168
- // 订阅频道接收消息
169
- RedisUtils.subscribe("notification:channel", MessageDTO.class, msg -> {
170
- log.info("收到消息: {}", msg);
171
- // 处理消息逻辑
172
- });
94
+ RedisUtils.publish("notification:channel", messageObj, msg -> { log.info("已发布: {}", msg); });
95
+ RedisUtils.subscribe("notification:channel", MessageDTO.class, msg -> { /* 处理消息 */ });
173
96
  ```
174
97
 
175
98
  ### 1.4 限流控制
@@ -177,259 +100,95 @@ RedisUtils.subscribe("notification:channel", MessageDTO.class, msg -> {
177
100
  ```java
178
101
  import org.redisson.api.RateType;
179
102
 
180
- // 限流控制(默认无超时)
181
- // 允许每10秒内最多100个请求
182
- long remaining = RedisUtils.rateLimiter("api:limit:user:123",
183
- RateType.OVERALL, 100, 10);
103
+ // 每10秒最多100个请求
104
+ long remaining = RedisUtils.rateLimiter("api:limit:user:123", RateType.OVERALL, 100, 10);
184
105
  if (remaining == -1) {
185
- throw new ServiceException("请求过于频繁,请稍后再试");
106
+ throw new ServiceException("请求过于频繁");
186
107
  }
187
108
 
188
- // 限流控制(带超时)
189
- // 允许每10秒内最多100个请求,超时时间5
190
- long remaining = RedisUtils.rateLimiter("api:limit:user:123",
191
- RateType.OVERALL, 100, 10, 5);
109
+ // 带超时版本(5秒超时)
110
+ long remaining = RedisUtils.rateLimiter("api:limit:user:123", RateType.OVERALL, 100, 10, 5);
192
111
  ```
193
112
 
194
113
  ### 1.5 原子操作
195
114
 
196
115
  ```java
197
- // 设置原子长整型值
198
116
  RedisUtils.setAtomicValue("counter:view", 0L);
199
-
200
- // 获取原子长整型值
201
117
  long value = RedisUtils.getAtomicValue("counter:view");
202
-
203
- // 原子递增
204
- long newValue = RedisUtils.incrAtomicValue("counter:view");
205
-
206
- // 原子递减
207
- long newValue = RedisUtils.decrAtomicValue("counter:view");
118
+ long newVal = RedisUtils.incrAtomicValue("counter:view");
119
+ long newVal = RedisUtils.decrAtomicValue("counter:view");
208
120
  ```
209
121
 
210
122
  ### 1.6 Key 操作
211
123
 
212
124
  ```java
213
- // 获取匹配模式的所有key(注意:会忽略租户隔离)
125
+ // ⚠️ keys() 和 deleteKeys() 会忽略租户隔离
214
126
  Collection<String> keys = RedisUtils.keys("user:*");
215
127
 
216
- // 通过扫描参数获取key列表
217
- KeysScanOptions options = KeysScanOptions.defaults()
218
- .pattern("user:*")
219
- .chunkSize(1000)
220
- .limit(100);
128
+ KeysScanOptions options = KeysScanOptions.defaults().pattern("user:*").chunkSize(1000).limit(100);
221
129
  Collection<String> keys = RedisUtils.keys(options);
222
130
 
223
- // 按模式批量删除key(注意:会忽略租户隔离)
224
131
  RedisUtils.deleteKeys("temp:*");
225
-
226
- // 检查key是否存在
227
132
  Boolean exists = RedisUtils.hasKey("user:123");
228
133
  ```
229
134
 
230
- ### 1.7 分布式锁(需自行注入 RedissonClient)
231
-
232
- > ⚠️ **注意**:`RedisUtils` 不提供 `getLock()` 方法,需要直接注入 `RedissonClient` 使用。
233
-
234
- ```java
235
- import org.redisson.api.RLock;
236
- import org.redisson.api.RedissonClient;
237
-
238
- @Service
239
- @RequiredArgsConstructor
240
- public class OrderServiceImpl implements IOrderService {
241
-
242
- private final RedissonClient redissonClient; // 直接注入
243
-
244
- public void processOrder(Long orderId) {
245
- // 获取锁对象
246
- RLock lock = redissonClient.getLock("lock:order:" + orderId);
247
-
248
- try {
249
- // 尝试加锁,最多等待10秒,锁定30秒后自动释放
250
- boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
251
- if (locked) {
252
- try {
253
- // 执行业务逻辑
254
- doProcess(orderId);
255
- } finally {
256
- // 释放锁(必须在finally中!)
257
- lock.unlock();
258
- }
259
- } else {
260
- throw new ServiceException("获取锁失败,请稍后重试");
261
- }
262
- } catch (InterruptedException e) {
263
- Thread.currentThread().interrupt();
264
- throw new ServiceException("获取锁被中断");
265
- }
266
- }
267
- }
268
- ```
269
-
270
- ### 1.8 监听机制
135
+ ### 1.7 监听机制
271
136
 
272
- ```java
273
- import org.redisson.api.listener.ExpiredObjectListener;
274
- import org.redisson.api.listener.DeletedObjectListener;
275
-
276
- // 注册过期监听器(需要开启Redis的notify-keyspace-events配置)
277
- RedisUtils.addObjectListener("user:123", (ExpiredObjectListener) name -> {
278
- log.info("缓存已过期: {}", name);
279
- });
280
-
281
- // 注册删除监听器
282
- RedisUtils.addObjectListener("user:123", (DeletedObjectListener) name -> {
283
- log.info("缓存已删除: {}", name);
284
- });
285
-
286
- // 注册List/Set/Map监听器(同样使用具体子接口)
287
- RedisUtils.addListListener("myList", (ExpiredObjectListener) name -> { ... });
288
- RedisUtils.addSetListener("mySet", (DeletedObjectListener) name -> { ... });
289
- RedisUtils.addMapListener("myMap", (ExpiredObjectListener) name -> { ... });
290
- ```
137
+ 详见 [references/listeners.md](references/listeners.md)
291
138
 
292
139
  ---
293
140
 
294
141
  ## 二、CacheUtils 工具类
295
142
 
296
- CacheUtils 提供了统一的 Spring Cache 操作接口。
297
-
298
143
  ```java
299
144
  import org.dromara.common.redis.utils.CacheUtils;
300
- ```
301
-
302
- ### 2.1 基本操作
303
-
304
- ```java
305
- // 保存缓存值
306
- CacheUtils.put("userCache", "user:123", userObj);
307
145
 
308
- // 获取缓存值
309
- User user = CacheUtils.get("userCache", "user:123");
310
-
311
- // 删除指定缓存项
312
- CacheUtils.evict("userCache", "user:123");
313
-
314
- // 清空指定缓存组的所有数据
315
- CacheUtils.clear("userCache");
316
- ```
317
-
318
- ### 2.2 使用场景
319
-
320
- ```java
321
- @Service
322
- public class UserServiceImpl implements IUserService {
323
-
324
- public User getUserById(Long userId) {
325
- // 先从缓存获取
326
- User user = CacheUtils.get(CacheNames.SYS_USER_NAME, userId);
327
- if (user != null) {
328
- return user;
329
- }
330
-
331
- // 缓存未命中,从数据库查询
332
- user = userMapper.selectById(userId);
333
-
334
- // 存入缓存
335
- if (user != null) {
336
- CacheUtils.put(CacheNames.SYS_USER_NAME, userId, user);
337
- }
338
-
339
- return user;
340
- }
341
-
342
- public void updateUser(User user) {
343
- // 更新数据库
344
- userMapper.updateById(user);
345
-
346
- // 清除缓存
347
- CacheUtils.evict(CacheNames.SYS_USER_NAME, user.getUserId());
348
- }
349
- }
146
+ CacheUtils.put("userCache", "user:123", userObj); // 保存
147
+ User user = CacheUtils.get("userCache", "user:123"); // 获取
148
+ CacheUtils.evict("userCache", "user:123"); // 删除
149
+ CacheUtils.clear("userCache"); // 清空整组
350
150
  ```
351
151
 
352
152
  ---
353
153
 
354
154
  ## 三、CacheNames 缓存名称常量
355
155
 
356
- > 位置:`ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java`
156
+ > 位置:`ruoyi-common/ruoyi-common-core/.../constant/CacheNames.java`
357
157
 
358
- ### 3.1 命名格式
158
+ ### 命名格式
359
159
 
360
160
  ```
361
161
  cacheNames#ttl#maxIdleTime#maxSize#local
362
-
363
- - ttl: 过期时间(0表示不过期,默认0)
364
- - maxIdleTime: 最大空闲时间(LRU清理,0表示不检测,默认0)
365
- - maxSize: 最大长度(LRU清理,0表示无限,默认0)
162
+ - ttl: 过期时间(0=不过期,默认0)
163
+ - maxIdleTime: 最大空闲时间(0=不检测,默认0)
164
+ - maxSize: 最大条数(0=无限,默认0)
366
165
  - local: 本地缓存(1开启,0关闭,默认1)
367
166
  ```
368
167
 
369
- ### 3.2 格式示例
370
-
371
168
  ```java
372
- // 60秒过期
373
- "test#60s"
374
-
375
- // 不过期,60秒空闲清理
376
- "test#0#60s"
377
-
378
- // 不过期,1分钟空闲清理,最大1000条
379
- "test#0#1m#1000"
380
-
381
- // 1小时过期,不检测空闲,最大500条
382
- "test#1h#0#500"
383
-
384
- // 1小时过期,不检测空闲,最大500条,关闭本地缓存
385
- "test#1h#0#500#0"
169
+ "test#60s" // 60秒过期
170
+ "test#0#60s" // 不过期,60秒空闲清理
171
+ "test#0#1m#1000" // 不过期,1分钟空闲,最大1000条
172
+ "test#1h#0#500#0" // 1小时过期,最大500条,关闭本地缓存
386
173
  ```
387
174
 
388
- ### 3.3 已定义常量
175
+ ### 已定义常量
389
176
 
390
177
  ```java
391
178
  public interface CacheNames {
392
-
393
- /** 演示案例:60秒过期,10分钟空闲,最大20条 */
394
179
  String DEMO_CACHE = "demo:cache#60s#10m#20";
395
-
396
- /** 系统配置 */
397
180
  String SYS_CONFIG = "sys_config";
398
-
399
- /** 数据字典 */
400
181
  String SYS_DICT = "sys_dict";
401
-
402
- /** 数据字典类型 */
403
182
  String SYS_DICT_TYPE = "sys_dict_type";
404
-
405
- /** 租户(30天过期,全局Key) */
406
183
  String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
407
-
408
- /** 客户端(30天过期,全局Key) */
409
184
  String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
410
-
411
- /** 用户账户(30天过期) */
412
185
  String SYS_USER_NAME = "sys_user_name#30d";
413
-
414
- /** 用户名称(30天过期) */
415
186
  String SYS_NICKNAME = "sys_nickname#30d";
416
-
417
- /** 部门(30天过期) */
418
187
  String SYS_DEPT = "sys_dept#30d";
419
-
420
- /** OSS内容(30天过期) */
421
188
  String SYS_OSS = "sys_oss#30d";
422
-
423
- /** 角色自定义权限(30天过期) */
424
189
  String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
425
-
426
- /** 部门及以下权限(30天过期) */
427
190
  String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
428
-
429
- /** OSS配置(全局Key) */
430
191
  String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
431
-
432
- /** 在线用户 */
433
192
  String ONLINE_TOKEN = "online_tokens";
434
193
  }
435
194
  ```
@@ -438,14 +197,9 @@ public interface CacheNames {
438
197
 
439
198
  ## 四、缓存注解使用规范
440
199
 
441
- ### 4.1 @Cacheable - 查询缓存
200
+ ### @Cacheable - 查询缓存
442
201
 
443
202
  ```java
444
- import org.springframework.cache.annotation.Cacheable;
445
-
446
- /**
447
- * 根据字典类型查询字典数据
448
- */
449
203
  @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
450
204
  @Override
451
205
  public List<SysDictDataVo> listDictDataByType(String dictType) {
@@ -454,112 +208,65 @@ public List<SysDictDataVo> listDictDataByType(String dictType) {
454
208
  }
455
209
  List<SysDictData> dictDataList = dictDataMapper.selectByType(dictType);
456
210
  if (CollUtil.isEmpty(dictDataList)) {
457
- return Collections.emptyList();
211
+ return Collections.emptyList(); // 返回空列表防止缓存穿透
458
212
  }
459
213
  return MapstructUtils.convert(dictDataList, SysDictDataVo.class);
460
214
  }
461
215
  ```
462
216
 
463
- ### 4.2 @CachePut - 更新缓存
217
+ ### @CachePut / @CacheEvict
464
218
 
465
219
  ```java
466
- import org.springframework.cache.annotation.CachePut;
467
-
468
- /**
469
- * 新增字典类型
470
- */
471
220
  @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType")
472
- @Override
473
- public List<SysDictDataVo> insertDictType(SysDictTypeBo bo) {
474
- SysDictType dictType = MapstructUtils.convert(bo, SysDictType.class);
475
- boolean success = dictTypeMapper.insert(dictType) > 0;
476
- if (success) {
477
- // 新增类型下无数据,返回空列表防止缓存穿透
478
- return Collections.emptyList();
479
- }
480
- throw new ServiceException("新增字典类型失败");
481
- }
482
- ```
221
+ public List<SysDictDataVo> insertDictType(SysDictTypeBo bo) { ... }
483
222
 
484
- ### 4.3 @CacheEvict - 清除缓存
485
-
486
- ```java
487
- import org.springframework.cache.annotation.CacheEvict;
488
-
489
- // 清除单个缓存
490
223
  @CacheEvict(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
491
- public void deleteDictType(String dictType) {
492
- dictTypeMapper.deleteByDictType(dictType);
493
- }
224
+ public void deleteDictType(String dictType) { ... }
494
225
 
495
- // 清除整个缓存组
496
226
  @CacheEvict(cacheNames = CacheNames.SYS_DICT, allEntries = true)
497
- public void clearAllDictCache() {
498
- // 清空所有字典缓存
499
- }
227
+ public void clearAllDictCache() { ... }
500
228
  ```
501
229
 
502
- ### 4.4 ⚠️ 重要:返回值不能使用不可变集合
230
+ ### 返回值禁止使用不可变集合
503
231
 
504
232
  ```java
505
- // ❌ 错误:使用 List.of() 会导致缓存序列化失败
506
- @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
507
- public List<SysDictDataVo> listDictDataByType(String dictType) {
508
- return List.of(data1, data2); // ❌ 禁止!
509
- }
510
-
511
- // ❌ 错误:使用 Set.of()
512
- @Cacheable(cacheNames = CacheNames.SYS_USER_NAME, key = "#userId")
513
- public Set<String> getUserRoles(Long userId) {
514
- return Set.of("admin", "user"); // ❌ 禁止!
515
- }
516
-
517
- // ❌ 错误:使用 Map.of()
518
- @Cacheable(cacheNames = CacheNames.SYS_CONFIG, key = "#configKey")
519
- public Map<String, Object> getConfig(String configKey) {
520
- return Map.of("key1", "value1"); // ❌ 禁止!
521
- }
233
+ // ❌ 禁止!@Cacheable 返回值序列化失败
234
+ return List.of(data1, data2);
235
+ return Set.of("admin", "user");
236
+ return Map.of("key1", "value1");
522
237
 
523
238
  // ✅ 正确:使用可变集合
524
- @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
525
- public List<SysDictDataVo> listDictDataByType(String dictType) {
526
- List<SysDictDataVo> result = new ArrayList<>();
527
- result.add(data1);
528
- result.add(data2);
529
- return result; // ✅ 正确
530
- }
239
+ List<SysDictDataVo> result = new ArrayList<>();
240
+ result.add(data1);
241
+ return result;
531
242
  ```
532
243
 
533
244
  ---
534
245
 
535
- ## 五、分布式锁最佳实践
536
-
537
- > ⚠️ **重要**:`RedisUtils` 不提供分布式锁方法,需要直接注入 `RedissonClient` 使用。
246
+ ## 五、分布式锁
538
247
 
539
- ### 5.1 防止重复提交
248
+ > RedisUtils **不提供** `getLock()`,需直接注入 `RedissonClient`。
540
249
 
541
250
  ```java
542
251
  @Service
543
252
  @RequiredArgsConstructor
544
253
  public class OrderServiceImpl implements IOrderService {
545
254
 
546
- private final RedissonClient redissonClient; // 直接注入
547
- private final OrderMapper orderMapper;
255
+ private final RedissonClient redissonClient;
548
256
 
549
257
  public void submitOrder(OrderBo orderBo) {
550
258
  String lockKey = "lock:submit:order:" + LoginHelper.getUserId();
551
- RLock lock = redissonClient.getLock(lockKey); // ✅ 使用 redissonClient
259
+ RLock lock = redissonClient.getLock(lockKey);
552
260
 
553
261
  try {
554
262
  boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
555
263
  if (!locked) {
556
264
  throw new ServiceException("请勿重复提交订单");
557
265
  }
558
-
559
266
  try {
560
267
  orderMapper.insert(orderBo);
561
268
  } finally {
562
- lock.unlock();
269
+ lock.unlock(); // 必须在 finally 中释放
563
270
  }
564
271
  } catch (InterruptedException e) {
565
272
  Thread.currentThread().interrupt();
@@ -569,271 +276,58 @@ public class OrderServiceImpl implements IOrderService {
569
276
  }
570
277
  ```
571
278
 
572
- ### 5.2 库存扣减
573
-
574
- ```java
575
- @Service
576
- @RequiredArgsConstructor
577
- public class GoodsServiceImpl implements IGoodsService {
578
-
579
- private final RedissonClient redissonClient; // 直接注入
580
- private final GoodsMapper goodsMapper;
581
-
582
- public void deductStock(Long goodsId, Integer quantity) {
583
- String lockKey = "lock:stock:" + goodsId;
584
- RLock lock = redissonClient.getLock(lockKey); // ✅ 使用 redissonClient
585
-
586
- try {
587
- boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
588
- if (!locked) {
589
- throw new ServiceException("系统繁忙,请稍后重试");
590
- }
591
-
592
- try {
593
- Goods goods = goodsMapper.selectById(goodsId);
594
- if (goods.getStock() < quantity) {
595
- throw new ServiceException("库存不足");
596
- }
597
- goods.setStock(goods.getStock() - quantity);
598
- goodsMapper.updateById(goods);
599
- } finally {
600
- lock.unlock();
601
- }
602
- } catch (InterruptedException e) {
603
- Thread.currentThread().interrupt();
604
- throw new ServiceException("库存扣减被中断");
605
- }
606
- }
607
- }
608
- ```
609
-
610
- ### 5.3 分布式锁规范
611
-
612
- 1. **锁的粒度要细**:锁的范围越小越好,避免锁住不必要的代码
613
- 2. **设置合理的超时时间**:根据业务执行时间设置,避免死锁
614
- 3. **必须在 finally 中释放锁**:确保锁一定会被释放
615
- 4. **处理 InterruptedException**:正确处理中断异常
616
- 5. **锁 key 要有业务含义**:便于排查问题
617
-
618
279
  ---
619
280
 
620
281
  ## 六、缓存 Key 命名规范
621
282
 
622
- ### 6.1 命名格式
623
-
624
- ```
625
- {业务模块}:{功能}:{具体标识}
626
283
  ```
284
+ 格式:{业务模块}:{功能}:{具体标识}
627
285
 
628
- ### 6.2 命名示例
629
-
630
- ```java
631
- // 用户缓存
632
- "user:info:123" // 用户信息
633
- "user:roles:123" // 用户角色
634
- "user:permissions:123" // 用户权限
635
-
636
- // 字典缓存
637
- "dict:data:sys_user_sex" // 字典数据
638
- "dict:type:sys_user_sex" // 字典类型
639
-
640
- // 订单缓存
641
- "order:info:20240101001" // 订单信息
642
- "order:status:20240101001" // 订单状态
643
-
644
- // 分布式锁
645
- "lock:order:20240101001" // 订单锁
646
- "lock:stock:123" // 库存锁
647
- "lock:submit:order:456" // 提交锁
648
-
649
- // 限流
650
- "limit:api:user:123" // 用户API限流
651
- "limit:sms:18888888888" // 短信限流
286
+ 示例:
287
+ user:info:123 user:roles:123 user:permissions:123
288
+ dict:data:sys_user_sex order:info:20240101001 order:status:20240101001
289
+ lock:order:20240101001 lock:stock:123 lock:submit:order:456
290
+ limit:api:user:123 limit:sms:18888888888
652
291
  ```
653
292
 
654
293
  ---
655
294
 
656
- ## 七、常见问题解决方案
657
-
658
- ### 7.1 缓存穿透
659
-
660
- **问题**:查询一个不存在的数据,缓存和数据库都没有,导致每次请求都打到数据库。
295
+ ## 七、租户隔离注意
661
296
 
662
297
  ```java
663
- @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
664
- public List<SysDictDataVo> listDictDataByType(String dictType) {
665
- List<SysDictData> dictDataList = dictDataMapper.selectByType(dictType);
666
- if (CollUtil.isEmpty(dictDataList)) {
667
- // 返回空列表而不是null,防止缓存穿透
668
- return Collections.emptyList();
669
- }
670
- return MapstructUtils.convert(dictDataList, SysDictDataVo.class);
671
- }
672
- ```
673
-
674
- ### 7.2 缓存雪崩
675
-
676
- **问题**:大量缓存同时过期,导致请求全部打到数据库。
677
-
678
- ```java
679
- // 设置随机过期时间,避免同时过期
680
- long baseTime = 30 * 60; // 30分钟
681
- long randomTime = ThreadLocalRandom.current().nextLong(5 * 60); // 随机0-5分钟
682
- Duration duration = Duration.ofSeconds(baseTime + randomTime);
683
- RedisUtils.setCacheObject("user:info:" + userId, userInfo, duration);
684
- ```
685
-
686
- ### 7.3 缓存击穿
687
-
688
- **问题**:热点数据过期瞬间,大量请求同时打到数据库。
689
-
690
- ```java
691
- @Service
692
- @RequiredArgsConstructor
693
- public class UserServiceImpl implements IUserService {
694
-
695
- private final RedissonClient redissonClient; // 直接注入
696
- private final UserMapper userMapper;
697
-
698
- public User getUserById(Long userId) {
699
- String cacheKey = "user:info:" + userId;
700
-
701
- // 先从缓存获取
702
- User user = RedisUtils.getCacheObject(cacheKey);
703
- if (user != null) {
704
- return user;
705
- }
706
-
707
- // 缓存未命中,使用分布式锁
708
- String lockKey = "lock:user:" + userId;
709
- RLock lock = redissonClient.getLock(lockKey); // ✅ 使用 redissonClient
710
-
711
- try {
712
- boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
713
- if (locked) {
714
- try {
715
- // 双重检查
716
- user = RedisUtils.getCacheObject(cacheKey);
717
- if (user != null) {
718
- return user;
719
- }
720
-
721
- // 从数据库查询
722
- user = userMapper.selectById(userId);
723
-
724
- // 存入缓存
725
- if (user != null) {
726
- RedisUtils.setCacheObject(cacheKey, user, Duration.ofMinutes(30));
727
- }
728
-
729
- return user;
730
- } finally {
731
- lock.unlock();
732
- }
733
- }
734
- } catch (InterruptedException e) {
735
- Thread.currentThread().interrupt();
736
- }
737
-
738
- // 获取锁失败,直接查询数据库
739
- return userMapper.selectById(userId);
740
- }
741
- }
742
- ```
743
-
744
- ### 7.4 缓存与数据库一致性
745
-
746
- ```java
747
- // 方案1:先更新数据库,再删除缓存(推荐)
748
- @Transactional(rollbackFor = Exception.class)
749
- public void updateUser(UserBo userBo) {
750
- User user = MapstructUtils.convert(userBo, User.class);
751
- userMapper.updateById(user);
752
-
753
- // 删除缓存
754
- RedisUtils.deleteObject("user:info:" + user.getUserId());
755
- }
756
-
757
- // 方案2:使用 @CachePut 更新缓存
758
- @CachePut(cacheNames = CacheNames.SYS_USER_NAME, key = "#result.userId")
759
- @Transactional(rollbackFor = Exception.class)
760
- public UserVo updateUser(UserBo userBo) {
761
- User user = MapstructUtils.convert(userBo, User.class);
762
- userMapper.updateById(user);
763
-
764
- User updatedUser = userMapper.selectById(user.getUserId());
765
- return MapstructUtils.convert(updatedUser, UserVo.class);
766
- }
767
- ```
768
-
769
- ### 7.5 租户隔离问题
770
-
771
- **问题**:`RedisUtils.keys()` 和 `RedisUtils.deleteKeys()` 会忽略租户隔离。
772
-
773
- ```java
774
- // 手动拼接租户ID
298
+ // ⚠️ keys() deleteKeys() 会忽略租户隔离,需手动拼接
775
299
  String tenantId = LoginHelper.getTenantId();
776
- String pattern = tenantId + ":user:*";
777
- Collection<String> keys = RedisUtils.keys(pattern);
300
+ Collection<String> keys = RedisUtils.keys(tenantId + ":user:*");
778
301
 
779
- // 或者使用 CacheUtils,它会自动处理租户隔离
302
+ // CacheUtils 会自动处理租户隔离
780
303
  CacheUtils.clear(CacheNames.SYS_USER_NAME);
781
304
  ```
782
305
 
783
306
  ---
784
307
 
785
- ## 八、核心文件位置
308
+ ## 八、注意事项速查
786
309
 
787
- | 文件 | 位置 |
788
- |------|------|
789
- | RedisUtils | `ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java` |
790
- | CacheUtils | `ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java` |
791
- | CacheNames | `ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java` |
792
- | GlobalConstants | `ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java` |
310
+ 1. **@Cacheable 返回值不能使用不可变集合**(List.of()、Set.of()、Map.of())
311
+ 2. **分布式锁必须在 finally 中释放**
312
+ 3. **keys() deleteKeys() 会忽略租户隔离**
313
+ 4. **返回空列表而不是 null**,防止缓存穿透
314
+ 5. **设置随机过期时间偏移**,避免缓存雪崩
315
+ 6. **热点数据用分布式锁 + 双重检查**,防止缓存击穿
793
316
 
794
317
  ---
795
318
 
796
- ## 九、快速参考
319
+ ## 九、核心文件位置
797
320
 
798
- ### 9.1 常用操作速查
799
-
800
- ```java
801
- // 基础缓存
802
- RedisUtils.setCacheObject(key, value); // 存储
803
- RedisUtils.setCacheObject(key, value, Duration.ofMinutes(30)); // 存储并设置过期时间
804
- Object value = RedisUtils.getCacheObject(key); // 获取
805
- RedisUtils.deleteObject(key); // 删除
806
- boolean exists = RedisUtils.isExistsObject(key); // 检查是否存在
807
-
808
- // 分布式锁(需注入 RedissonClient,RedisUtils 不提供 getLock)
809
- RLock lock = redissonClient.getLock(lockKey);
810
- boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
811
- lock.unlock(); // 必须在 finally 中释放
812
-
813
- // 限流
814
- long remaining = RedisUtils.rateLimiter(key, RateType.OVERALL, 100, 10);
815
-
816
- // 缓存注解
817
- @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType") // 查询缓存
818
- @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType") // 更新缓存
819
- @CacheEvict(cacheNames = CacheNames.SYS_DICT, key = "#dictType") // 清除缓存
820
- ```
821
-
822
- ### 9.2 注意事项
823
-
824
- 1. ⚠️ **@Cacheable 返回值不能使用不可变集合**(List.of()、Set.of()、Map.of())
825
- 2. ⚠️ **分布式锁必须在 finally 中释放**
826
- 3. ⚠️ **keys() 和 deleteKeys() 会忽略租户隔离**,需要手动拼接租户ID
827
- 4. ⚠️ **缓存 key 要有业务含义**,便于排查问题
828
- 5. ⚠️ **设置合理的过期时间**,避免缓存雪崩
829
- 6. ⚠️ **返回空列表而不是 null**,防止缓存穿透
321
+ | 文件 | 位置 |
322
+ |------|------|
323
+ | RedisUtils | `ruoyi-common/ruoyi-common-redis/.../utils/RedisUtils.java` |
324
+ | CacheUtils | `ruoyi-common/ruoyi-common-redis/.../utils/CacheUtils.java` |
325
+ | CacheNames | `ruoyi-common/ruoyi-common-core/.../constant/CacheNames.java` |
326
+ | GlobalConstants | `ruoyi-common/ruoyi-common-core/.../constant/GlobalConstants.java` |
830
327
 
831
328
  ---
832
329
 
833
330
  ## 多项目适配说明
834
331
 
835
-
836
- ## 注意事项
837
-
838
332
  - 如果需要 leniu-tengyun-core 项目的 Redis 开发规范,请使用 `leniu-redis-cache` skill
839
333
  - leniu-tengyun-core 的 RedisUtil 工具类方法名与 RuoYi-Vue-Plus 的 RedisUtils 不同