ai-engineering-init 1.7.0 → 1.10.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 (139) hide show
  1. package/.claude/agents/bug-analyzer.md +103 -0
  2. package/.claude/agents/code-reviewer.md +115 -5
  3. package/.claude/agents/image-reader.md +154 -0
  4. package/.claude/agents/loki-runner.md +80 -0
  5. package/.claude/agents/mysql-runner.md +81 -0
  6. package/.claude/agents/requirements-analyzer.md +162 -0
  7. package/.claude/agents/task-fetcher.md +75 -0
  8. package/.claude/commands/dev.md +29 -0
  9. package/.claude/commands/next.md +31 -1
  10. package/.claude/commands/progress.md +23 -1
  11. package/.claude/hooks/skill-forced-eval.js +46 -62
  12. package/.claude/settings.json +10 -1
  13. package/.claude/skills/api-development/SKILL.md +179 -130
  14. package/.claude/skills/architecture-design/SKILL.md +102 -212
  15. package/.claude/skills/backend-annotations/SKILL.md +166 -220
  16. package/.claude/skills/bug-detective/SKILL.md +225 -186
  17. package/.claude/skills/code-patterns/SKILL.md +127 -244
  18. package/.claude/skills/collaborating-with-codex/SKILL.md +96 -113
  19. package/.claude/skills/crud-development/SKILL.md +226 -307
  20. package/.claude/skills/data-permission/SKILL.md +131 -202
  21. package/.claude/skills/database-ops/SKILL.md +158 -355
  22. package/.claude/skills/error-handler/SKILL.md +224 -285
  23. package/.claude/skills/file-oss-management/SKILL.md +174 -169
  24. package/.claude/skills/git-workflow/SKILL.md +123 -341
  25. package/.claude/skills/json-serialization/SKILL.md +121 -137
  26. package/.claude/skills/performance-doctor/SKILL.md +83 -89
  27. package/.claude/skills/redis-cache/SKILL.md +134 -185
  28. package/.claude/skills/scheduled-jobs/SKILL.md +187 -224
  29. package/.claude/skills/security-guard/SKILL.md +168 -276
  30. package/.claude/skills/sms-mail/SKILL.md +266 -228
  31. package/.claude/skills/social-login/SKILL.md +257 -195
  32. package/.claude/skills/tenant-management/SKILL.md +172 -188
  33. package/.claude/skills/utils-toolkit/SKILL.md +214 -222
  34. package/.claude/skills/websocket-sse/SKILL.md +251 -172
  35. package/.claude/skills/workflow-engine/SKILL.md +178 -250
  36. package/.codex/skills/api-development/SKILL.md +179 -130
  37. package/.codex/skills/architecture-design/SKILL.md +102 -212
  38. package/.codex/skills/backend-annotations/SKILL.md +166 -220
  39. package/.codex/skills/bug-detective/SKILL.md +225 -186
  40. package/.codex/skills/code-patterns/SKILL.md +127 -244
  41. package/.codex/skills/collaborating-with-codex/SKILL.md +96 -113
  42. package/.codex/skills/crud-development/SKILL.md +226 -307
  43. package/.codex/skills/data-permission/SKILL.md +131 -202
  44. package/.codex/skills/database-ops/SKILL.md +158 -355
  45. package/.codex/skills/dev/SKILL.md +476 -131
  46. package/.codex/skills/error-handler/SKILL.md +224 -285
  47. package/.codex/skills/file-oss-management/SKILL.md +174 -169
  48. package/.codex/skills/git-workflow/SKILL.md +123 -341
  49. package/.codex/skills/json-serialization/SKILL.md +121 -137
  50. package/.codex/skills/next/SKILL.md +186 -42
  51. package/.codex/skills/performance-doctor/SKILL.md +83 -89
  52. package/.codex/skills/progress/SKILL.md +147 -76
  53. package/.codex/skills/redis-cache/SKILL.md +134 -185
  54. package/.codex/skills/scheduled-jobs/SKILL.md +187 -224
  55. package/.codex/skills/security-guard/SKILL.md +168 -276
  56. package/.codex/skills/sms-mail/SKILL.md +266 -228
  57. package/.codex/skills/social-login/SKILL.md +257 -195
  58. package/.codex/skills/tenant-management/SKILL.md +172 -188
  59. package/.codex/skills/utils-toolkit/SKILL.md +214 -222
  60. package/.codex/skills/websocket-sse/SKILL.md +251 -172
  61. package/.codex/skills/workflow-engine/SKILL.md +178 -250
  62. package/.cursor/agents/bug-analyzer.md +102 -0
  63. package/.cursor/agents/code-reviewer.md +80 -97
  64. package/.cursor/agents/image-reader.md +154 -0
  65. package/.cursor/agents/loki-runner.md +80 -0
  66. package/.cursor/agents/mysql-runner.md +81 -0
  67. package/.cursor/agents/project-manager.md +1 -1
  68. package/.cursor/agents/requirements-analyzer.md +141 -0
  69. package/.cursor/agents/task-fetcher.md +75 -0
  70. package/.cursor/hooks/cursor-skill-eval.js +66 -6
  71. package/.cursor/skills/api-development/SKILL.md +179 -130
  72. package/.cursor/skills/architecture-design/SKILL.md +102 -212
  73. package/.cursor/skills/backend-annotations/SKILL.md +166 -220
  74. package/.cursor/skills/bug-detective/SKILL.md +225 -186
  75. package/.cursor/skills/code-patterns/SKILL.md +127 -244
  76. package/.cursor/skills/collaborating-with-codex/SKILL.md +96 -113
  77. package/.cursor/skills/crud-development/SKILL.md +226 -307
  78. package/.cursor/skills/data-permission/SKILL.md +131 -202
  79. package/.cursor/skills/database-ops/SKILL.md +158 -355
  80. package/.cursor/skills/error-handler/SKILL.md +224 -285
  81. package/.cursor/skills/file-oss-management/SKILL.md +174 -169
  82. package/.cursor/skills/git-workflow/SKILL.md +123 -341
  83. package/.cursor/skills/json-serialization/SKILL.md +121 -137
  84. package/.cursor/skills/performance-doctor/SKILL.md +83 -89
  85. package/.cursor/skills/redis-cache/SKILL.md +134 -185
  86. package/.cursor/skills/scheduled-jobs/SKILL.md +187 -224
  87. package/.cursor/skills/security-guard/SKILL.md +168 -276
  88. package/.cursor/skills/sms-mail/SKILL.md +266 -228
  89. package/.cursor/skills/social-login/SKILL.md +257 -195
  90. package/.cursor/skills/tenant-management/SKILL.md +172 -188
  91. package/.cursor/skills/utils-toolkit/SKILL.md +214 -222
  92. package/.cursor/skills/websocket-sse/SKILL.md +251 -172
  93. package/.cursor/skills/workflow-engine/SKILL.md +178 -250
  94. package/AGENTS.md +117 -540
  95. package/CLAUDE.md +105 -117
  96. package/README.md +37 -6
  97. package/bin/index.js +5 -1
  98. package/package.json +1 -1
  99. package/src/skills/api-development/SKILL.md +179 -130
  100. package/src/skills/architecture-design/SKILL.md +102 -212
  101. package/src/skills/backend-annotations/SKILL.md +166 -220
  102. package/src/skills/bug-detective/SKILL.md +225 -186
  103. package/src/skills/code-patterns/SKILL.md +127 -244
  104. package/src/skills/collaborating-with-codex/SKILL.md +96 -113
  105. package/src/skills/crud-development/SKILL.md +226 -307
  106. package/src/skills/data-permission/SKILL.md +131 -202
  107. package/src/skills/database-ops/SKILL.md +158 -355
  108. package/src/skills/error-handler/SKILL.md +224 -285
  109. package/src/skills/file-oss-management/SKILL.md +174 -169
  110. package/src/skills/git-workflow/SKILL.md +123 -341
  111. package/src/skills/json-serialization/SKILL.md +121 -137
  112. package/src/skills/performance-doctor/SKILL.md +83 -89
  113. package/src/skills/redis-cache/SKILL.md +134 -185
  114. package/src/skills/scheduled-jobs/SKILL.md +187 -224
  115. package/src/skills/security-guard/SKILL.md +168 -276
  116. package/src/skills/sms-mail/SKILL.md +266 -228
  117. package/src/skills/social-login/SKILL.md +257 -195
  118. package/src/skills/tenant-management/SKILL.md +172 -188
  119. package/src/skills/utils-toolkit/SKILL.md +214 -222
  120. package/src/skills/websocket-sse/SKILL.md +251 -172
  121. package/src/skills/workflow-engine/SKILL.md +178 -250
  122. package/.claude/skills/skill-creator/LICENSE.txt +0 -202
  123. package/.claude/skills/skill-creator/SKILL.md +0 -479
  124. package/.claude/skills/skill-creator/agents/analyzer.md +0 -274
  125. package/.claude/skills/skill-creator/agents/comparator.md +0 -202
  126. package/.claude/skills/skill-creator/agents/grader.md +0 -223
  127. package/.claude/skills/skill-creator/assets/eval_review.html +0 -146
  128. package/.claude/skills/skill-creator/eval-viewer/generate_review.py +0 -471
  129. package/.claude/skills/skill-creator/eval-viewer/viewer.html +0 -1325
  130. package/.claude/skills/skill-creator/references/schemas.md +0 -430
  131. package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
  132. package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
  133. package/.claude/skills/skill-creator/scripts/generate_report.py +0 -326
  134. package/.claude/skills/skill-creator/scripts/improve_description.py +0 -248
  135. package/.claude/skills/skill-creator/scripts/package_skill.py +0 -136
  136. package/.claude/skills/skill-creator/scripts/quick_validate.py +0 -103
  137. package/.claude/skills/skill-creator/scripts/run_eval.py +0 -310
  138. package/.claude/skills/skill-creator/scripts/run_loop.py +0 -332
  139. package/.claude/skills/skill-creator/scripts/utils.py +0 -47
@@ -1,179 +1,212 @@
1
1
  ---
2
2
  name: tenant-management
3
3
  description: |
4
- 多租户数据隔离开发指南。基于 MyBatis-Plus 租户插件,自动 SQL/Redis/Cache 隔离。
5
-
4
+ 通用多租户架构设计指南。涵盖字段隔离、Schema 隔离、物理隔离三种模式的对比与实现要点。
6
5
  触发场景:
7
- - 为业务表添加租户隔离
8
- - 临时忽略租户过滤查询全量数据
9
- - 切换到其他租户执行操作
10
- - 配置排除租户过滤的表
6
+ - 设计多租户 SaaS 系统架构
7
+ - 选择租户隔离方案
8
+ - 处理跨租户数据操作
11
9
  - 定时任务遍历所有租户
10
+ - 配置 Redis / 缓存租户隔离
11
+ 触发词:多租户、租户隔离、SaaS、tenantId、跨租户、租户切换、数据隔离、Schema隔离、物理隔离、字段隔离
12
+ 注意:如果项目有专属技能(如 `leniu-data-permission` 或 `leniu-tenant`),优先使用专属版本。
13
+ ---
14
+
15
+ # 多租户架构设计指南
16
+
17
+ > 通用模板。如果项目有专属技能,优先使用。
18
+
19
+ ## 设计原则
20
+
21
+ 1. **隔离性**:租户数据必须完全隔离,不可跨租户访问。
22
+ 2. **透明性**:业务代码尽量不感知多租户,由框架层自动处理。
23
+ 3. **可扩展**:新增租户不需要改代码,配置即可上线。
24
+ 4. **运维友好**:备份恢复、数据迁移应以租户为单位。
12
25
 
13
- 触发词:多租户、租户隔离、TenantEntity、TenantHelper、tenantId、跨租户、ignore租户、动态租户、租户切换、SaaS、数据隔离
14
26
  ---
15
27
 
16
- # 多租户开发指南
28
+ ## 三种隔离模式对比
29
+
30
+ | 维度 | 字段隔离 | Schema 隔离 | 物理隔离(独立库) |
31
+ |------|---------|------------|-----------------|
32
+ | **实现方式** | 每张表加 `tenant_id` 字段 | 每个租户独立 Schema | 每个租户独立数据库 |
33
+ | **隔离强度** | 低(逻辑隔离) | 中(Schema 级) | 高(物理隔离) |
34
+ | **数据安全** | 依赖应用层正确过滤 | 数据库级隔离 | 最安全 |
35
+ | **资源利用** | 高(共享一切) | 中(共享实例) | 低(独占资源) |
36
+ | **扩展性** | 单库上限制约 | 单实例 Schema 数限制 | 水平扩展灵活 |
37
+ | **运维复杂度** | 低 | 中 | 高 |
38
+ | **备份恢复** | 复杂(需过滤) | 简单(按 Schema) | 最简单(按库) |
39
+ | **租户定制** | 困难 | 可支持 | 完全独立 |
40
+ | **成本** | 最低 | 中等 | 最高 |
41
+ | **适用场景** | 中小 SaaS、租户量大 | 中等规模、需一定隔离 | 大客户、强合规需求 |
17
42
 
18
- > **模块**:`ruoyi-common-tenant` | **核心类**:`TenantHelper`、`TenantEntity`
43
+ ---
19
44
 
20
- ## 一、Entity 规范
45
+ ## 实现模式
21
46
 
22
- ### 继承 TenantEntity
47
+ ### 模式一:字段隔离(最常见)
23
48
 
24
49
  ```java
25
- import org.dromara.common.tenant.core.TenantEntity;
26
-
27
- // TenantEntity 继承关系:TenantEntity → BaseEntity
28
- // TenantEntity 额外字段:tenantId
29
- // BaseEntity 字段:createDept, createBy, createTime, updateBy, updateTime, params
50
+ // 1. Entity 携带 tenant_id
51
+ public class [你的租户基类] extends [你的基础Entity] {
52
+ private String tenantId;
53
+ }
30
54
 
31
- @Data
32
- @EqualsAndHashCode(callSuper = true)
33
55
  @TableName("biz_order")
34
- public class BizOrder extends TenantEntity {
35
- @TableId(value = "id")
56
+ public class BizOrder extends [你的租户基类] {
36
57
  private Long id;
37
58
  private String orderNo;
38
- private String status;
39
59
  }
40
- ```
41
60
 
42
- ### 数据库表必须含 tenant_id
43
-
44
- ```sql
45
- CREATE TABLE biz_order (
46
- id BIGINT(20) NOT NULL COMMENT '主键ID',
47
- tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID', -- 必须有
48
- order_no VARCHAR(64) NOT NULL COMMENT '订单号',
49
- status CHAR(1) DEFAULT '0' COMMENT '状态',
50
- create_dept BIGINT(20) DEFAULT NULL COMMENT '创建部门',
51
- create_by BIGINT(20) DEFAULT NULL COMMENT '创建人',
52
- create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
53
- update_by BIGINT(20) DEFAULT NULL COMMENT '更新人',
54
- update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
55
- del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志',
56
- PRIMARY KEY (id),
57
- KEY idx_tenant_id (tenant_id)
58
- );
61
+ // 2. 数据库表必须含 tenant_id
62
+ // CREATE TABLE biz_order (
63
+ // id BIGINT NOT NULL,
64
+ // tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID',
65
+ // order_no VARCHAR(64) NOT NULL,
66
+ // PRIMARY KEY (id),
67
+ // KEY idx_tenant_id (tenant_id)
68
+ // );
69
+
70
+ // 3. MyBatis 拦截器自动追加条件
71
+ // SELECT * FROM biz_order WHERE tenant_id = '000001' AND ...
72
+ // INSERT INTO biz_order (tenant_id, ...) VALUES ('000001', ...)
59
73
  ```
60
74
 
61
- ---
62
-
63
- ## 二、TenantHelper 工具类
64
-
65
- **位置**:`org.dromara.common.tenant.helper.TenantHelper`
66
-
67
- ### API 速查
68
-
69
- | 方法 | 说明 | 返回值 |
70
- |------|------|--------|
71
- | `isEnable()` | 租户功能是否启用 | boolean |
72
- | `getTenantId()` | 获取当前租户ID(优先动态租户) | String |
73
- | `ignore(Runnable)` | 忽略租户执行(无返回值) | void |
74
- | `ignore(Supplier<T>)` | 忽略租户执行(有返回值) | T |
75
- | `dynamic(tenantId, Runnable)` | 切换租户执行(无返回值) | void |
76
- | `dynamic(tenantId, Supplier<T>)` | 切换租户执行(有返回值) | T |
77
- | `setDynamic(tenantId)` | 手动设置动态租户(需配合 clearDynamic) | void |
78
- | `setDynamic(tenantId, true)` | 设置全局动态租户(跨请求,存 Redis) | void |
79
- | `clearDynamic()` | 清除动态租户 | void |
80
-
81
- ### 核心用法
75
+ **核心工具类模式**:
82
76
 
83
77
  ```java
84
- import org.dromara.common.tenant.helper.TenantHelper;
78
+ public class [你的租户工具类] {
85
79
 
86
- // 1. 忽略租户(查全量数据)
87
- List<SysUser> allUsers = TenantHelper.ignore(() -> {
88
- return userMapper.selectList(null); // 不追加 tenant_id 条件
89
- });
80
+ // 获取当前租户ID(从请求上下文/ThreadLocal)
81
+ public static String getTenantId() { ... }
90
82
 
91
- // 2. 切换到指定租户(推荐用法)
92
- TenantHelper.dynamic("000001", () -> {
93
- userMapper.insert(user); // 使用租户 000001
94
- });
83
+ // 忽略租户过滤(查全量数据)
84
+ public static <T> T ignore(Supplier<T> supplier) {
85
+ // 临时关闭拦截器的租户条件追加
86
+ try {
87
+ setIgnore(true);
88
+ return supplier.get();
89
+ } finally {
90
+ setIgnore(false);
91
+ }
92
+ }
95
93
 
96
- // 3. 手动管理(复杂场景)
97
- TenantHelper.setDynamic("000001");
98
- try {
99
- userMapper.insert(user);
100
- } finally {
101
- TenantHelper.clearDynamic(); // 必须清理!
94
+ // 切换到指定租户执行
95
+ public static void dynamic(String tenantId, Runnable runnable) {
96
+ String original = getTenantId();
97
+ try {
98
+ setTenantId(tenantId);
99
+ runnable.run();
100
+ } finally {
101
+ setTenantId(original);
102
+ }
103
+ }
104
+
105
+ // 带返回值的租户切换
106
+ public static <T> T dynamic(String tenantId, Supplier<T> supplier) {
107
+ String original = getTenantId();
108
+ try {
109
+ setTenantId(tenantId);
110
+ return supplier.get();
111
+ } finally {
112
+ setTenantId(original);
113
+ }
114
+ }
102
115
  }
103
116
  ```
104
117
 
105
- ---
106
-
107
- ## 三、配置
118
+ **配置排除表**(不需要租户过滤的共享表):
108
119
 
109
120
  ```yaml
110
- # application.yml
111
121
  tenant:
112
122
  enable: true
113
- excludes: # 不追加 tenant_id 条件的表
123
+ excludes:
114
124
  - sys_menu
115
125
  - sys_dict_type
116
126
  - sys_dict_data
117
- - sys_oss_config
118
127
  - sys_config
119
128
  ```
120
129
 
121
- **自动配置组件**(`tenant.enable=true` 时生效):
130
+ ### 模式二:Schema 隔离
131
+
132
+ ```java
133
+ // 动态数据源 / 动态 Schema 切换
134
+ public class TenantSchemaResolver {
135
+ public String resolveSchema(String tenantId) {
136
+ return "tenant_" + tenantId; // tenant_000001, tenant_000002
137
+ }
138
+ }
139
+
140
+ // 通过 AbstractRoutingDataSource 或 MyBatis 拦截器切换 Schema
141
+ // SET search_path TO tenant_000001; (PostgreSQL)
142
+ // USE tenant_000001; (MySQL)
143
+ ```
144
+
145
+ ### 模式三:物理隔离(独立库)
146
+
147
+ ```java
148
+ // 每个租户对应独立的数据源配置
149
+ // 通过 AbstractRoutingDataSource 或动态数据源框架切换
150
+
151
+ public class TenantDataSourceRouter extends AbstractRoutingDataSource {
152
+ @Override
153
+ protected Object determineCurrentLookupKey() {
154
+ return [你的租户工具类].getTenantId();
155
+ }
156
+ }
157
+
158
+ // 典型用法
159
+ [你的租户工具类].doInTenant(tenantId, () -> {
160
+ // 自动路由到该租户的数据库
161
+ orderMapper.insert(order);
162
+ });
122
163
 
123
- | 组件 | 功能 |
124
- |------|------|
125
- | `TenantLineInnerInterceptor` | SQL 自动追加租户条件 |
126
- | `TenantKeyPrefixHandler` | Redis Key 自动添加租户前缀 |
127
- | `TenantSpringCacheManager` | Spring Cache 租户隔离 |
128
- | `TenantSaTokenDao` | Sa-Token 会话租户隔离 |
164
+ [你的租户工具类].doInSystem(() -> {
165
+ // 路由到系统公共库
166
+ configMapper.selectList(null);
167
+ });
168
+ ```
129
169
 
130
170
  ---
131
171
 
132
- ## 四、常见场景
172
+ ## 常见场景
133
173
 
134
174
  ### 场景 1:管理员查看所有租户数据
135
175
 
136
176
  ```java
137
- @SaCheckRole("superadmin")
138
- public List<SysUserVo> listAllTenantUsers() {
139
- return TenantHelper.ignore(() -> userMapper.selectVoList(null));
177
+ public List<UserVo> listAllTenantUsers() {
178
+ return [你的租户工具类].ignore(() -> userMapper.selectVoList(null));
140
179
  }
141
180
  ```
142
181
 
143
182
  ### 场景 2:跨租户数据同步
144
183
 
145
184
  ```java
146
- public void syncConfigToAllTenants(SysConfig config) {
147
- List<String> tenantIds = TenantHelper.ignore(() ->
185
+ public void syncConfigToAllTenants(Config config) {
186
+ List<String> tenantIds = [你的租户工具类].ignore(() ->
148
187
  tenantMapper.selectList(null).stream()
149
- .map(SysTenant::getTenantId).collect(Collectors.toList())
188
+ .map(Tenant::getTenantId).collect(Collectors.toList())
150
189
  );
151
190
  for (String tenantId : tenantIds) {
152
- TenantHelper.dynamic(tenantId, () -> {
153
- SysConfig existing = configMapper.selectByKey(config.getConfigKey());
154
- if (existing == null) configMapper.insert(config);
155
- else configMapper.updateById(config);
191
+ [你的租户工具类].dynamic(tenantId, () -> {
192
+ configMapper.insertOrUpdate(config);
156
193
  });
157
194
  }
158
195
  }
159
196
  ```
160
197
 
161
- ### 场景 3:定时任务处理所有租户
162
-
163
- > 详细示例见 `references/tenant-scenarios.md`
198
+ ### 场景 3:定时任务遍历所有租户
164
199
 
165
200
  ```java
166
201
  @Scheduled(cron = "0 0 2 * * ?")
167
202
  public void cleanupExpiredOrders() {
168
- List<SysTenant> tenants = TenantHelper.ignore(() ->
169
- tenantMapper.selectList(Wrappers.<SysTenant>lambdaQuery().eq(SysTenant::getStatus, "0"))
203
+ List<Tenant> tenants = [你的租户工具类].ignore(() ->
204
+ tenantMapper.selectByStatus("ACTIVE")
170
205
  );
171
- for (SysTenant tenant : tenants) {
206
+ for (Tenant tenant : tenants) {
172
207
  try {
173
- TenantHelper.dynamic(tenant.getTenantId(), () -> {
174
- orderMapper.delete(Wrappers.<Order>lambdaQuery()
175
- .eq(Order::getStatus, "CANCELLED")
176
- .lt(Order::getCreateTime, DateUtils.addDays(new Date(), -30)));
208
+ [你的租户工具类].dynamic(tenant.getTenantId(), () -> {
209
+ orderMapper.deleteExpired(30);
177
210
  });
178
211
  } catch (Exception e) {
179
212
  log.error("清理租户 {} 订单失败: {}", tenant.getTenantId(), e.getMessage());
@@ -182,9 +215,7 @@ public void cleanupExpiredOrders() {
182
215
  }
183
216
  ```
184
217
 
185
- ---
186
-
187
- ## 五、Redis 缓存隔离
218
+ ### 场景 4:Redis 缓存隔离
188
219
 
189
220
  ```
190
221
  原始 Key: user:info:1001
@@ -192,97 +223,50 @@ public void cleanupExpiredOrders() {
192
223
  实际 Key: 000001:user:info:1001 (租户 000001)
193
224
  ```
194
225
 
195
- **全局 Key(不隔离)**:使用 `GlobalConstants.GLOBAL_REDIS_KEY` 前缀
196
-
197
- ```java
198
- import org.dromara.common.core.constant.GlobalConstants;
226
+ 全局缓存(不隔离):使用特定前缀标识全局 Key
199
227
 
200
- // 全局缓存(所有租户共享)
201
- String globalKey = GlobalConstants.GLOBAL_REDIS_KEY + "config:site_name";
202
- RedisUtils.setCacheObject(globalKey, "网站名称");
228
+ ---
203
229
 
204
- // 租户隔离缓存(自动添加前缀)
205
- RedisUtils.setCacheObject("user:info:" + userId, userInfo);
206
- ```
230
+ ## 选型建议
207
231
 
208
- > `TenantHelper.ignore()` 中的 Redis 操作不添加租户前缀。
232
+ | 维度 | 字段隔离 | Schema 隔离 | 物理隔离 |
233
+ |------|---------|------------|---------|
234
+ | 租户数量 | 大量(100+) | 中等(10-100) | 少量(<20) |
235
+ | 数据量级 | 单租户数据量小 | 中等 | 大 |
236
+ | 合规要求 | 一般 | 中等 | 严格(金融、政务) |
237
+ | 预算 | 有限 | 中等 | 充足 |
238
+ | 定制化 | 几乎不需要 | 少量 | 高度定制 |
209
239
 
210
240
  ---
211
241
 
212
- ## 六、常见错误
213
-
214
- ### 1. 忘记继承 TenantEntity
242
+ ## 常见错误
215
243
 
216
244
  ```java
217
- // 继承 BaseEntity,没有 tenantId
218
- public class BizOrder extends BaseEntity { }
219
-
220
- // ✅ 继承 TenantEntity
221
- public class BizOrder extends TenantEntity { }
222
- ```
223
-
224
- ### 2. 数据库表缺少 tenant_id
225
-
226
- ```sql
227
- -- ❌ 表没有 tenant_id
228
- CREATE TABLE biz_order (id BIGINT NOT NULL, order_no VARCHAR(64));
229
-
230
- -- ✅ 添加 tenant_id
231
- CREATE TABLE biz_order (id BIGINT NOT NULL, tenant_id VARCHAR(20) DEFAULT '000000', order_no VARCHAR(64));
232
- ```
233
-
234
- ### 3. setDynamic 后忘记 clearDynamic
245
+ // 1. Entity 忘记继承租户基类 / 表缺少 tenant_id(字段隔离模式)
246
+ public class BizOrder extends BaseEntity { } // 缺少 tenantId
247
+ // 表中也没有 tenant_id 字段 -> 数据不隔离
235
248
 
236
- ```java
237
- // ❌ 租户ID泄漏到其他请求
238
- TenantHelper.setDynamic("000001");
249
+ // 2. 手动设置租户后忘记清理
250
+ [你的租户工具类].setTenantId("000001");
239
251
  userMapper.insert(user);
240
- // 忘记 clearDynamic()
241
-
242
- // ✅ 推荐使用 dynamic() 方法(自动清理)
243
- TenantHelper.dynamic("000001", () -> userMapper.insert(user));
244
- ```
252
+ // 忘记恢复原租户ID -> 后续请求使用错误租户
253
+ // 应使用 dynamic() 方法(自动恢复)
245
254
 
246
- ### 4. 事务中切换租户
247
-
248
- ```java
249
- // ❌ 事务内切换租户导致数据错乱
255
+ // 3. 事务中切换租户(字段隔离模式下可能数据错乱)
250
256
  @Transactional
251
257
  public void wrongMethod() {
252
- orderMapper.insert(order);
253
- TenantHelper.dynamic("000001", () -> logMapper.insert(log));
258
+ orderMapper.insert(order); // 租户 A
259
+ [你的租户工具类].dynamic("B", () -> logMapper.insert(log)); // 租户 B
260
+ // 如果回滚,租户 B 的数据不会回滚(不同事务)
254
261
  }
255
-
256
- // ✅ 使用独立事务
262
+ // 应使用独立事务
257
263
  @Transactional(propagation = Propagation.REQUIRES_NEW)
258
- public void saveLogToOtherTenant(String tenantId, Log log) {
259
- TenantHelper.dynamic(tenantId, () -> logMapper.insert(log));
260
- }
261
- ```
264
+ public void saveToOtherTenant(String tenantId, Log log) { ... }
262
265
 
263
- ### 5. 排除表配置不当
266
+ // 4. 共享表未排除租户过滤
267
+ // sys_dict_type 等共享表被加上 tenant_id 条件 -> 查不到数据
268
+ // 应在配置中排除这些表
264
269
 
265
- ```yaml
266
- # 业务表不应排除
267
- tenant:
268
- excludes:
269
- - biz_order
270
-
271
- # ✅ 只排除共享的系统表
272
- tenant:
273
- excludes:
274
- - sys_menu
275
- - sys_dict_type
270
+ // 5. 物理隔离模式下使用 tenant_id 字段(画蛇添足)
271
+ // 物理隔离已通过数据源区分租户,表中不需要 tenant_id
276
272
  ```
277
-
278
- ---
279
-
280
- ## 七、参考代码位置
281
-
282
- | 类型 | 位置 |
283
- |------|------|
284
- | 租户基类 | `ruoyi-common/ruoyi-common-tenant/.../core/TenantEntity.java` |
285
- | 租户助手 | `ruoyi-common/ruoyi-common-tenant/.../helper/TenantHelper.java` |
286
- | 租户处理器 | `ruoyi-common/ruoyi-common-tenant/.../handle/PlusTenantLineHandler.java` |
287
- | Redis Key 处理 | `ruoyi-common/ruoyi-common-tenant/.../handle/TenantKeyPrefixHandler.java` |
288
- | 配置文件 | `ruoyi-admin/src/main/resources/application.yml` |