ai-engineering-init 1.7.0 → 1.8.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 (118) hide show
  1. package/.claude/hooks/skill-forced-eval.js +46 -62
  2. package/.claude/settings.json +10 -1
  3. package/.claude/skills/api-development/SKILL.md +179 -130
  4. package/.claude/skills/architecture-design/SKILL.md +102 -212
  5. package/.claude/skills/backend-annotations/SKILL.md +166 -220
  6. package/.claude/skills/bug-detective/SKILL.md +225 -186
  7. package/.claude/skills/code-patterns/SKILL.md +127 -244
  8. package/.claude/skills/collaborating-with-codex/SKILL.md +96 -113
  9. package/.claude/skills/crud-development/SKILL.md +226 -307
  10. package/.claude/skills/data-permission/SKILL.md +131 -202
  11. package/.claude/skills/database-ops/SKILL.md +158 -355
  12. package/.claude/skills/error-handler/SKILL.md +224 -285
  13. package/.claude/skills/file-oss-management/SKILL.md +174 -169
  14. package/.claude/skills/git-workflow/SKILL.md +123 -341
  15. package/.claude/skills/json-serialization/SKILL.md +121 -137
  16. package/.claude/skills/performance-doctor/SKILL.md +83 -89
  17. package/.claude/skills/redis-cache/SKILL.md +134 -185
  18. package/.claude/skills/scheduled-jobs/SKILL.md +187 -224
  19. package/.claude/skills/security-guard/SKILL.md +168 -276
  20. package/.claude/skills/sms-mail/SKILL.md +266 -228
  21. package/.claude/skills/social-login/SKILL.md +257 -195
  22. package/.claude/skills/tenant-management/SKILL.md +172 -188
  23. package/.claude/skills/utils-toolkit/SKILL.md +214 -222
  24. package/.claude/skills/websocket-sse/SKILL.md +251 -172
  25. package/.claude/skills/workflow-engine/SKILL.md +178 -250
  26. package/.codex/skills/api-development/SKILL.md +179 -130
  27. package/.codex/skills/architecture-design/SKILL.md +102 -212
  28. package/.codex/skills/backend-annotations/SKILL.md +166 -220
  29. package/.codex/skills/bug-detective/SKILL.md +225 -186
  30. package/.codex/skills/code-patterns/SKILL.md +127 -244
  31. package/.codex/skills/collaborating-with-codex/SKILL.md +96 -113
  32. package/.codex/skills/crud-development/SKILL.md +226 -307
  33. package/.codex/skills/data-permission/SKILL.md +131 -202
  34. package/.codex/skills/database-ops/SKILL.md +158 -355
  35. package/.codex/skills/error-handler/SKILL.md +224 -285
  36. package/.codex/skills/file-oss-management/SKILL.md +174 -169
  37. package/.codex/skills/git-workflow/SKILL.md +123 -341
  38. package/.codex/skills/json-serialization/SKILL.md +121 -137
  39. package/.codex/skills/performance-doctor/SKILL.md +83 -89
  40. package/.codex/skills/redis-cache/SKILL.md +134 -185
  41. package/.codex/skills/scheduled-jobs/SKILL.md +187 -224
  42. package/.codex/skills/security-guard/SKILL.md +168 -276
  43. package/.codex/skills/sms-mail/SKILL.md +266 -228
  44. package/.codex/skills/social-login/SKILL.md +257 -195
  45. package/.codex/skills/tenant-management/SKILL.md +172 -188
  46. package/.codex/skills/utils-toolkit/SKILL.md +214 -222
  47. package/.codex/skills/websocket-sse/SKILL.md +251 -172
  48. package/.codex/skills/workflow-engine/SKILL.md +178 -250
  49. package/.cursor/hooks/cursor-skill-eval.js +66 -6
  50. package/.cursor/skills/api-development/SKILL.md +179 -130
  51. package/.cursor/skills/architecture-design/SKILL.md +102 -212
  52. package/.cursor/skills/backend-annotations/SKILL.md +166 -220
  53. package/.cursor/skills/bug-detective/SKILL.md +225 -186
  54. package/.cursor/skills/code-patterns/SKILL.md +127 -244
  55. package/.cursor/skills/collaborating-with-codex/SKILL.md +96 -113
  56. package/.cursor/skills/crud-development/SKILL.md +226 -307
  57. package/.cursor/skills/data-permission/SKILL.md +131 -202
  58. package/.cursor/skills/database-ops/SKILL.md +158 -355
  59. package/.cursor/skills/error-handler/SKILL.md +224 -285
  60. package/.cursor/skills/file-oss-management/SKILL.md +174 -169
  61. package/.cursor/skills/git-workflow/SKILL.md +123 -341
  62. package/.cursor/skills/json-serialization/SKILL.md +121 -137
  63. package/.cursor/skills/performance-doctor/SKILL.md +83 -89
  64. package/.cursor/skills/redis-cache/SKILL.md +134 -185
  65. package/.cursor/skills/scheduled-jobs/SKILL.md +187 -224
  66. package/.cursor/skills/security-guard/SKILL.md +168 -276
  67. package/.cursor/skills/sms-mail/SKILL.md +266 -228
  68. package/.cursor/skills/social-login/SKILL.md +257 -195
  69. package/.cursor/skills/tenant-management/SKILL.md +172 -188
  70. package/.cursor/skills/utils-toolkit/SKILL.md +214 -222
  71. package/.cursor/skills/websocket-sse/SKILL.md +251 -172
  72. package/.cursor/skills/workflow-engine/SKILL.md +178 -250
  73. package/AGENTS.md +49 -540
  74. package/CLAUDE.md +73 -119
  75. package/README.md +37 -6
  76. package/bin/index.js +5 -1
  77. package/package.json +1 -1
  78. package/src/skills/api-development/SKILL.md +179 -130
  79. package/src/skills/architecture-design/SKILL.md +102 -212
  80. package/src/skills/backend-annotations/SKILL.md +166 -220
  81. package/src/skills/bug-detective/SKILL.md +225 -186
  82. package/src/skills/code-patterns/SKILL.md +127 -244
  83. package/src/skills/collaborating-with-codex/SKILL.md +96 -113
  84. package/src/skills/crud-development/SKILL.md +226 -307
  85. package/src/skills/data-permission/SKILL.md +131 -202
  86. package/src/skills/database-ops/SKILL.md +158 -355
  87. package/src/skills/error-handler/SKILL.md +224 -285
  88. package/src/skills/file-oss-management/SKILL.md +174 -169
  89. package/src/skills/git-workflow/SKILL.md +123 -341
  90. package/src/skills/json-serialization/SKILL.md +121 -137
  91. package/src/skills/performance-doctor/SKILL.md +83 -89
  92. package/src/skills/redis-cache/SKILL.md +134 -185
  93. package/src/skills/scheduled-jobs/SKILL.md +187 -224
  94. package/src/skills/security-guard/SKILL.md +168 -276
  95. package/src/skills/sms-mail/SKILL.md +266 -228
  96. package/src/skills/social-login/SKILL.md +257 -195
  97. package/src/skills/tenant-management/SKILL.md +172 -188
  98. package/src/skills/utils-toolkit/SKILL.md +214 -222
  99. package/src/skills/websocket-sse/SKILL.md +251 -172
  100. package/src/skills/workflow-engine/SKILL.md +178 -250
  101. package/.claude/skills/skill-creator/LICENSE.txt +0 -202
  102. package/.claude/skills/skill-creator/SKILL.md +0 -479
  103. package/.claude/skills/skill-creator/agents/analyzer.md +0 -274
  104. package/.claude/skills/skill-creator/agents/comparator.md +0 -202
  105. package/.claude/skills/skill-creator/agents/grader.md +0 -223
  106. package/.claude/skills/skill-creator/assets/eval_review.html +0 -146
  107. package/.claude/skills/skill-creator/eval-viewer/generate_review.py +0 -471
  108. package/.claude/skills/skill-creator/eval-viewer/viewer.html +0 -1325
  109. package/.claude/skills/skill-creator/references/schemas.md +0 -430
  110. package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
  111. package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
  112. package/.claude/skills/skill-creator/scripts/generate_report.py +0 -326
  113. package/.claude/skills/skill-creator/scripts/improve_description.py +0 -248
  114. package/.claude/skills/skill-creator/scripts/package_skill.py +0 -136
  115. package/.claude/skills/skill-creator/scripts/quick_validate.py +0 -103
  116. package/.claude/skills/skill-creator/scripts/run_eval.py +0 -310
  117. package/.claude/skills/skill-creator/scripts/run_loop.py +0 -332
  118. 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` |