1. 多租户支持与资源隔离概述

多租户支持与资源隔离是现代SaaS平台的核心技术,通过租户隔离、数据隔离、资源隔离、权限控制实现多租户共享基础设施的同时保证数据安全和业务隔离。本文将详细介绍多租户架构设计、资源隔离策略、数据隔离实现、权限控制机制和租户管理的完整解决方案。

1.1 核心功能

  1. 多租户架构: 租户识别、租户上下文、租户管理
  2. 资源隔离: 计算资源、存储资源、网络资源隔离
  3. 数据隔离: 数据库级、Schema级、表级隔离
  4. 权限控制: 租户权限、用户权限、资源权限
  5. 租户管理: 租户生命周期、资源分配、监控管理

1.2 技术架构

1
2
3
4
5
用户请求 → 租户识别 → 资源路由 → 数据隔离 → 权限验证
↓ ↓ ↓ ↓ ↓
多租户 → 上下文管理 → 资源分配 → 数据访问 → 业务处理
↓ ↓ ↓ ↓ ↓
SaaS平台 → 租户管理 → 资源监控 → 数据安全 → 结果返回

2. 多租户架构配置

2.1 Maven依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Spring Boot Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- HikariCP连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>

<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>

2.2 多租户架构配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
* 多租户架构配置类
*/
@Configuration
public class MultiTenantConfig {

@Value("${multi-tenant.isolation-strategy:DATABASE}")
private String isolationStrategy;

@Value("${multi-tenant.default-tenant:default}")
private String defaultTenant;

@Value("${multi-tenant.header-name:X-Tenant-ID}")
private String tenantHeaderName;

/**
* 多租户配置属性
*/
@Bean
public MultiTenantProperties multiTenantProperties() {
return MultiTenantProperties.builder()
.isolationStrategy(isolationStrategy)
.defaultTenant(defaultTenant)
.tenantHeaderName(tenantHeaderName)
.build();
}

/**
* 租户上下文管理器
*/
@Bean
public TenantContextManager tenantContextManager() {
return new TenantContextManager(multiTenantProperties());
}

/**
* 租户资源管理器
*/
@Bean
public TenantResourceManager tenantResourceManager() {
return new TenantResourceManager(multiTenantProperties());
}

/**
* 租户权限管理器
*/
@Bean
public TenantPermissionManager tenantPermissionManager() {
return new TenantPermissionManager(multiTenantProperties());
}
}

/**
* 多租户配置属性
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MultiTenantProperties {
private String isolationStrategy; // DATABASE, SCHEMA, TABLE, ROW
private String defaultTenant;
private String tenantHeaderName;

// 数据隔离配置
private Map<String, DataSourceConfig> tenantDataSources = new HashMap<>();
private boolean enableDynamicDataSource = true;
private int maxTenantConnections = 100;

// 资源隔离配置
private boolean enableResourceIsolation = true;
private int maxTenantResources = 1000;
private int tenantResourceTimeout = 300; // 秒

// 权限控制配置
private boolean enablePermissionControl = true;
private boolean enableCrossTenantAccess = false;
private List<String> allowedCrossTenantOperations = new ArrayList<>();

// 缓存配置
private boolean enableTenantCache = true;
private int tenantCacheExpireTime = 3600; // 秒
private String tenantCachePrefix = "tenant:";

// 监控配置
private boolean enableTenantMonitoring = true;
private int monitoringInterval = 60; // 秒
private boolean enableResourceMetrics = true;
}

/**
* 数据源配置
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DataSourceConfig {
private String url;
private String username;
private String password;
private String driverClassName;
private int maxActive;
private int maxIdle;
private int minIdle;
private int initialSize;
private String schema;
private String database;
}

3. 租户上下文管理

3.1 租户上下文管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/**
* 租户上下文管理器
*/
@Component
public class TenantContextManager {

private final MultiTenantProperties properties;
private final TenantService tenantService;
private final RedisTemplate<String, Object> redisTemplate;

public TenantContextManager(MultiTenantProperties properties) {
this.properties = properties;
this.tenantService = null; // 注入
this.redisTemplate = null; // 注入
}

/**
* 设置租户上下文
* @param tenantId 租户ID
* @param request HTTP请求
*/
public void setTenantContext(String tenantId, HttpServletRequest request) {
try {
// 1. 验证租户ID
if (tenantId == null || tenantId.isEmpty()) {
tenantId = properties.getDefaultTenant();
}

// 2. 获取租户信息
TenantInfo tenantInfo = getTenantInfo(tenantId);

// 3. 设置租户上下文
TenantContext.setTenantId(tenantId);
TenantContext.setTenantInfo(tenantInfo);

// 4. 设置请求属性
request.setAttribute("tenantId", tenantId);
request.setAttribute("tenantInfo", tenantInfo);

log.info("设置租户上下文成功: tenantId={}", tenantId);

} catch (Exception e) {
log.error("设置租户上下文失败: tenantId={}", tenantId, e);
throw new TenantContextException("设置租户上下文失败", e);
}
}

/**
* 获取租户信息
* @param tenantId 租户ID
* @return 租户信息
*/
public TenantInfo getTenantInfo(String tenantId) {
try {
// 1. 从缓存获取
if (properties.isEnableTenantCache()) {
TenantInfo cachedInfo = getTenantInfoFromCache(tenantId);
if (cachedInfo != null) {
return cachedInfo;
}
}

// 2. 从数据库获取
TenantInfo tenantInfo = tenantService.getTenantInfo(tenantId);

// 3. 缓存租户信息
if (tenantInfo != null && properties.isEnableTenantCache()) {
cacheTenantInfo(tenantId, tenantInfo);
}

return tenantInfo;

} catch (Exception e) {
log.error("获取租户信息失败: tenantId={}", tenantId, e);
return getDefaultTenantInfo();
}
}

/**
* 验证租户权限
* @param tenantId 租户ID
* @param operation 操作
* @return 是否有权限
*/
public boolean validateTenantPermission(String tenantId, String operation) {
try {
// 1. 获取租户信息
TenantInfo tenantInfo = getTenantInfo(tenantId);
if (tenantInfo == null) {
return false;
}

// 2. 检查租户状态
if (!"ACTIVE".equals(tenantInfo.getStatus())) {
log.warn("租户状态异常: tenantId={}, status={}", tenantId, tenantInfo.getStatus());
return false;
}

// 3. 检查操作权限
if (properties.isEnablePermissionControl()) {
return checkOperationPermission(tenantId, operation);
}

return true;

} catch (Exception e) {
log.error("验证租户权限失败: tenantId={}, operation={}", tenantId, operation, e);
return false;
}
}

/**
* 从缓存获取租户信息
* @param tenantId 租户ID
* @return 租户信息
*/
private TenantInfo getTenantInfoFromCache(String tenantId) {
try {
String cacheKey = properties.getTenantCachePrefix() + tenantId;
return (TenantInfo) redisTemplate.opsForValue().get(cacheKey);
} catch (Exception e) {
log.warn("从缓存获取租户信息失败: tenantId={}", tenantId, e);
return null;
}
}

/**
* 缓存租户信息
* @param tenantId 租户ID
* @param tenantInfo 租户信息
*/
private void cacheTenantInfo(String tenantId, TenantInfo tenantInfo) {
try {
String cacheKey = properties.getTenantCachePrefix() + tenantId;
redisTemplate.opsForValue().set(cacheKey, tenantInfo,
Duration.ofSeconds(properties.getTenantCacheExpireTime()));
} catch (Exception e) {
log.warn("缓存租户信息失败: tenantId={}", tenantId, e);
}
}

/**
* 检查操作权限
* @param tenantId 租户ID
* @param operation 操作
* @return 是否有权限
*/
private boolean checkOperationPermission(String tenantId, String operation) {
try {
// 实现权限检查逻辑
// 这里可以根据具体的权限模型来实现
return true;
} catch (Exception e) {
log.error("检查操作权限失败: tenantId={}, operation={}", tenantId, operation, e);
return false;
}
}

/**
* 获取默认租户信息
* @return 默认租户信息
*/
private TenantInfo getDefaultTenantInfo() {
return TenantInfo.builder()
.tenantId(properties.getDefaultTenant())
.tenantName("Default Tenant")
.tenantCode("default")
.status("ACTIVE")
.isolationStrategy(properties.getIsolationStrategy())
.build();
}
}

/**
* 租户上下文
*/
public class TenantContext {
private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>();
private static final ThreadLocal<TenantInfo> TENANT_INFO = new ThreadLocal<>();
private static final ThreadLocal<Map<String, Object>> TENANT_ATTRIBUTES = new ThreadLocal<>();

/**
* 设置租户ID
* @param tenantId 租户ID
*/
public static void setTenantId(String tenantId) {
TENANT_ID.set(tenantId);
}

/**
* 获取租户ID
* @return 租户ID
*/
public static String getTenantId() {
return TENANT_ID.get();
}

/**
* 设置租户信息
* @param tenantInfo 租户信息
*/
public static void setTenantInfo(TenantInfo tenantInfo) {
TENANT_INFO.set(tenantInfo);
}

/**
* 获取租户信息
* @return 租户信息
*/
public static TenantInfo getTenantInfo() {
return TENANT_INFO.get();
}

/**
* 设置租户属性
* @param key 属性键
* @param value 属性值
*/
public static void setTenantAttribute(String key, Object value) {
Map<String, Object> attributes = TENANT_ATTRIBUTES.get();
if (attributes == null) {
attributes = new HashMap<>();
TENANT_ATTRIBUTES.set(attributes);
}
attributes.put(key, value);
}

/**
* 获取租户属性
* @param key 属性键
* @return 属性值
*/
public static Object getTenantAttribute(String key) {
Map<String, Object> attributes = TENANT_ATTRIBUTES.get();
return attributes != null ? attributes.get(key) : null;
}

/**
* 清除租户上下文
*/
public static void clear() {
TENANT_ID.remove();
TENANT_INFO.remove();
TENANT_ATTRIBUTES.remove();
}
}

/**
* 租户信息模型
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TenantInfo {
private String tenantId;
private String tenantName;
private String tenantCode;
private String status; // ACTIVE, INACTIVE, SUSPENDED
private String isolationStrategy;
private String databaseName;
private String schemaName;
private Map<String, Object> properties = new HashMap<>();
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

4. 资源隔离管理

4.1 资源隔离管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/**
* 租户资源管理器
*/
@Component
public class TenantResourceManager {

private final MultiTenantProperties properties;
private final RedisTemplate<String, Object> redisTemplate;
private final Map<String, ResourcePool> tenantResourcePools = new ConcurrentHashMap<>();

public TenantResourceManager(MultiTenantProperties properties) {
this.properties = properties;
this.redisTemplate = null; // 注入
}

/**
* 分配租户资源
* @param tenantId 租户ID
* @param resourceType 资源类型
* @param resourceCount 资源数量
* @return 是否成功
*/
public boolean allocateTenantResource(String tenantId, String resourceType, int resourceCount) {
try {
// 1. 检查租户资源限制
if (!checkTenantResourceLimit(tenantId, resourceType, resourceCount)) {
log.warn("租户资源分配超限: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount);
return false;
}

// 2. 获取租户资源池
ResourcePool resourcePool = getTenantResourcePool(tenantId);

// 3. 分配资源
boolean allocated = resourcePool.allocateResource(resourceType, resourceCount);

if (allocated) {
// 4. 记录资源分配
recordResourceAllocation(tenantId, resourceType, resourceCount);

log.info("租户资源分配成功: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount);
}

return allocated;

} catch (Exception e) {
log.error("分配租户资源失败: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount, e);
return false;
}
}

/**
* 释放租户资源
* @param tenantId 租户ID
* @param resourceType 资源类型
* @param resourceCount 资源数量
* @return 是否成功
*/
public boolean releaseTenantResource(String tenantId, String resourceType, int resourceCount) {
try {
// 1. 获取租户资源池
ResourcePool resourcePool = getTenantResourcePool(tenantId);

// 2. 释放资源
boolean released = resourcePool.releaseResource(resourceType, resourceCount);

if (released) {
// 3. 记录资源释放
recordResourceRelease(tenantId, resourceType, resourceCount);

log.info("租户资源释放成功: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount);
}

return released;

} catch (Exception e) {
log.error("释放租户资源失败: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount, e);
return false;
}
}

/**
* 获取租户资源使用情况
* @param tenantId 租户ID
* @return 资源使用情况
*/
public Map<String, ResourceUsage> getTenantResourceUsage(String tenantId) {
try {
ResourcePool resourcePool = getTenantResourcePool(tenantId);
return resourcePool.getResourceUsage();
} catch (Exception e) {
log.error("获取租户资源使用情况失败: tenantId={}", tenantId, e);
return new HashMap<>();
}
}

/**
* 检查租户资源限制
* @param tenantId 租户ID
* @param resourceType 资源类型
* @param resourceCount 资源数量
* @return 是否允许
*/
private boolean checkTenantResourceLimit(String tenantId, String resourceType, int resourceCount) {
try {
// 1. 获取租户资源限制
Map<String, Integer> resourceLimits = getTenantResourceLimits(tenantId);

// 2. 检查资源限制
Integer limit = resourceLimits.get(resourceType);
if (limit != null) {
Map<String, ResourceUsage> currentUsage = getTenantResourceUsage(tenantId);
ResourceUsage usage = currentUsage.get(resourceType);
int currentCount = usage != null ? usage.getUsedCount() : 0;

if (currentCount + resourceCount > limit) {
return false;
}
}

return true;

} catch (Exception e) {
log.error("检查租户资源限制失败: tenantId={}, resourceType={}", tenantId, resourceType, e);
return false;
}
}

/**
* 获取租户资源池
* @param tenantId 租户ID
* @return 资源池
*/
private ResourcePool getTenantResourcePool(String tenantId) {
return tenantResourcePools.computeIfAbsent(tenantId, k -> new ResourcePool(tenantId));
}

/**
* 获取租户资源限制
* @param tenantId 租户ID
* @return 资源限制
*/
private Map<String, Integer> getTenantResourceLimits(String tenantId) {
try {
String cacheKey = "tenant_resource_limits:" + tenantId;
Map<String, Integer> limits = (Map<String, Integer>) redisTemplate.opsForValue().get(cacheKey);

if (limits == null) {
// 从数据库获取默认限制
limits = getDefaultResourceLimits();
redisTemplate.opsForValue().set(cacheKey, limits, Duration.ofHours(1));
}

return limits;

} catch (Exception e) {
log.error("获取租户资源限制失败: tenantId={}", tenantId, e);
return getDefaultResourceLimits();
}
}

/**
* 获取默认资源限制
* @return 默认资源限制
*/
private Map<String, Integer> getDefaultResourceLimits() {
Map<String, Integer> limits = new HashMap<>();
limits.put("CPU", 1000); // CPU核心数
limits.put("MEMORY", 8192); // 内存MB
limits.put("STORAGE", 100000); // 存储MB
limits.put("NETWORK", 1000); // 网络带宽Mbps
limits.put("CONNECTIONS", 100); // 连接数
return limits;
}

/**
* 记录资源分配
* @param tenantId 租户ID
* @param resourceType 资源类型
* @param resourceCount 资源数量
*/
private void recordResourceAllocation(String tenantId, String resourceType, int resourceCount) {
try {
String cacheKey = "tenant_resource_allocation:" + tenantId;
Map<String, Integer> allocations = (Map<String, Integer>) redisTemplate.opsForValue().get(cacheKey);

if (allocations == null) {
allocations = new HashMap<>();
}

allocations.put(resourceType, allocations.getOrDefault(resourceType, 0) + resourceCount);
redisTemplate.opsForValue().set(cacheKey, allocations, Duration.ofHours(1));

} catch (Exception e) {
log.error("记录资源分配失败: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount, e);
}
}

/**
* 记录资源释放
* @param tenantId 租户ID
* @param resourceType 资源类型
* @param resourceCount 资源数量
*/
private void recordResourceRelease(String tenantId, String resourceType, int resourceCount) {
try {
String cacheKey = "tenant_resource_allocation:" + tenantId;
Map<String, Integer> allocations = (Map<String, Integer>) redisTemplate.opsForValue().get(cacheKey);

if (allocations != null) {
int currentCount = allocations.getOrDefault(resourceType, 0);
int newCount = Math.max(0, currentCount - resourceCount);

if (newCount == 0) {
allocations.remove(resourceType);
} else {
allocations.put(resourceType, newCount);
}

redisTemplate.opsForValue().set(cacheKey, allocations, Duration.ofHours(1));
}

} catch (Exception e) {
log.error("记录资源释放失败: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount, e);
}
}
}

/**
* 资源池
*/
public class ResourcePool {
private final String tenantId;
private final Map<String, ResourceUsage> resourceUsage = new ConcurrentHashMap<>();

public ResourcePool(String tenantId) {
this.tenantId = tenantId;
}

/**
* 分配资源
* @param resourceType 资源类型
* @param resourceCount 资源数量
* @return 是否成功
*/
public synchronized boolean allocateResource(String resourceType, int resourceCount) {
try {
ResourceUsage usage = resourceUsage.computeIfAbsent(resourceType,
k -> new ResourceUsage(resourceType, 0, 0));

// 检查资源限制
if (usage.getUsedCount() + resourceCount > usage.getMaxCount()) {
return false;
}

usage.setUsedCount(usage.getUsedCount() + resourceCount);
return true;

} catch (Exception e) {
log.error("分配资源失败: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount, e);
return false;
}
}

/**
* 释放资源
* @param resourceType 资源类型
* @param resourceCount 资源数量
* @return 是否成功
*/
public synchronized boolean releaseResource(String resourceType, int resourceCount) {
try {
ResourceUsage usage = resourceUsage.get(resourceType);
if (usage == null) {
return false;
}

int newCount = Math.max(0, usage.getUsedCount() - resourceCount);
usage.setUsedCount(newCount);

return true;

} catch (Exception e) {
log.error("释放资源失败: tenantId={}, resourceType={}, count={}",
tenantId, resourceType, resourceCount, e);
return false;
}
}

/**
* 获取资源使用情况
* @return 资源使用情况
*/
public Map<String, ResourceUsage> getResourceUsage() {
return new HashMap<>(resourceUsage);
}
}

/**
* 资源使用情况
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResourceUsage {
private String resourceType;
private int usedCount;
private int maxCount;
}

5. 数据隔离实现

5.1 数据隔离实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
* 数据隔离管理器
*/
@Component
public class DataIsolationManager {

private final MultiTenantProperties properties;
private final Map<String, DataSource> tenantDataSources = new ConcurrentHashMap<>();
private final DataSource defaultDataSource;

public DataIsolationManager(MultiTenantProperties properties) {
this.properties = properties;
this.defaultDataSource = createDefaultDataSource();
}

/**
* 获取租户数据源
* @param tenantId 租户ID
* @return 数据源
*/
public DataSource getTenantDataSource(String tenantId) {
try {
// 1. 获取租户信息
TenantInfo tenantInfo = TenantContext.getTenantInfo();
if (tenantInfo == null) {
return defaultDataSource;
}

// 2. 根据隔离策略获取数据源
switch (tenantInfo.getIsolationStrategy()) {
case "DATABASE":
return getDatabaseDataSource(tenantId);
case "SCHEMA":
return getSchemaDataSource(tenantId);
case "TABLE":
return getTableDataSource(tenantId);
case "ROW":
return getRowDataSource(tenantId);
default:
return defaultDataSource;
}

} catch (Exception e) {
log.error("获取租户数据源失败: tenantId={}", tenantId, e);
return defaultDataSource;
}
}

/**
* 获取数据库级数据源
* @param tenantId 租户ID
* @return 数据源
*/
private DataSource getDatabaseDataSource(String tenantId) {
String dataSourceKey = "tenant_" + tenantId;

if (tenantDataSources.containsKey(dataSourceKey)) {
return tenantDataSources.get(dataSourceKey);
}

// 创建新的数据源
DataSource dataSource = createTenantDataSource(tenantId);
tenantDataSources.put(dataSourceKey, dataSource);

return dataSource;
}

/**
* 获取Schema级数据源
* @param tenantId 租户ID
* @return 数据源
*/
private DataSource getSchemaDataSource(String tenantId) {
// Schema级隔离使用同一个数据库,不同的Schema
return defaultDataSource;
}

/**
* 获取表级数据源
* @param tenantId 租户ID
* @return 数据源
*/
private DataSource getTableDataSource(String tenantId) {
// 表级隔离使用同一个数据库,表名包含租户ID
return defaultDataSource;
}

/**
* 获取行级数据源
* @param tenantId 租户ID
* @return 数据源
*/
private DataSource getRowDataSource(String tenantId) {
// 行级隔离使用同一个数据库,表中包含租户ID字段
return defaultDataSource;
}

/**
* 创建租户数据源
* @param tenantId 租户ID
* @return 数据源
*/
private DataSource createTenantDataSource(String tenantId) {
try {
DataSourceConfig config = properties.getTenantDataSources().get(tenantId);
if (config == null) {
config = getDefaultDataSourceConfig(tenantId);
}

HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(config.getUrl());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
hikariConfig.setDriverClassName(config.getDriverClassName());
hikariConfig.setMaximumPoolSize(config.getMaxActive());
hikariConfig.setMinimumIdle(config.getMinIdle());
hikariConfig.setConnectionTimeout(30000);
hikariConfig.setIdleTimeout(600000);
hikariConfig.setMaxLifetime(1800000);

return new HikariDataSource(hikariConfig);

} catch (Exception e) {
log.error("创建租户数据源失败: tenantId={}", tenantId, e);
return defaultDataSource;
}
}

/**
* 创建默认数据源
* @return 默认数据源
*/
private DataSource createDefaultDataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl("jdbc:mysql://localhost:3306/default_tenant?useUnicode=true&characterEncoding=utf8");
hikariConfig.setUsername("root");
hikariConfig.setPassword("password");
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariConfig.setMaximumPoolSize(20);
hikariConfig.setMinimumIdle(5);

return new HikariDataSource(hikariConfig);
}

/**
* 获取默认数据源配置
* @param tenantId 租户ID
* @return 数据源配置
*/
private DataSourceConfig getDefaultDataSourceConfig(String tenantId) {
return DataSourceConfig.builder()
.url("jdbc:mysql://localhost:3306/tenant_" + tenantId + "?useUnicode=true&characterEncoding=utf8")
.username("root")
.password("password")
.driverClassName("com.mysql.cj.jdbc.Driver")
.maxActive(20)
.maxIdle(10)
.minIdle(5)
.initialSize(5)
.database("tenant_" + tenantId)
.build();
}
}

/**
* 多租户拦截器
*/
@Component
public class MultiTenantInterceptor implements HandlerInterceptor {

private final TenantContextManager tenantContextManager;

public MultiTenantInterceptor(TenantContextManager tenantContextManager) {
this.tenantContextManager = tenantContextManager;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
// 1. 获取租户ID
String tenantId = getTenantIdFromRequest(request);

// 2. 设置租户上下文
tenantContextManager.setTenantContext(tenantId, request);

return true;

} catch (Exception e) {
log.error("多租户拦截器处理失败", e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return false;
}
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除租户上下文
TenantContext.clear();
}

/**
* 从请求中获取租户ID
* @param request HTTP请求
* @return 租户ID
*/
private String getTenantIdFromRequest(HttpServletRequest request) {
// 1. 从请求头获取
String tenantId = request.getHeader("X-Tenant-ID");

// 2. 从请求参数获取
if (tenantId == null) {
tenantId = request.getParameter("tenantId");
}

// 3. 从JWT Token获取
if (tenantId == null) {
tenantId = getTenantIdFromToken(request);
}

return tenantId;
}

/**
* 从JWT Token获取租户ID
* @param request HTTP请求
* @return 租户ID
*/
private String getTenantIdFromToken(HttpServletRequest request) {
try {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
// 解析JWT Token获取租户ID
// 这里需要实现JWT解析逻辑
return parseTenantIdFromToken(token);
}
} catch (Exception e) {
log.warn("从Token获取租户ID失败", e);
}
return null;
}

/**
* 解析Token中的租户ID
* @param token JWT Token
* @return 租户ID
*/
private String parseTenantIdFromToken(String token) {
// 实现JWT Token解析逻辑
// 这里需要根据实际的JWT实现来解析
return null;
}
}

6. 多租户控制器

6.1 多租户控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* 多租户控制器
*/
@RestController
@RequestMapping("/api/v1/tenant")
public class MultiTenantController {

@Autowired
private TenantService tenantService;

@Autowired
private TenantResourceManager resourceManager;

@Autowired
private TenantContextManager contextManager;

/**
* 获取租户信息
*/
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> getTenantInfo() {
try {
TenantInfo tenantInfo = TenantContext.getTenantInfo();

Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("tenantInfo", tenantInfo);

return ResponseEntity.ok(response);

} catch (Exception e) {
log.error("获取租户信息失败", e);

Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取租户信息失败: " + e.getMessage());

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

/**
* 切换租户
*/
@PostMapping("/switch")
public ResponseEntity<Map<String, Object>> switchTenant(@RequestParam String tenantId) {
try {
// 1. 验证租户ID
if (tenantId == null || tenantId.isEmpty()) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "租户ID不能为空");
return ResponseEntity.badRequest().body(response);
}

// 2. 验证租户权限
if (!contextManager.validateTenantPermission(tenantId, "SWITCH")) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无权限切换租户");
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
}

// 3. 获取租户信息
TenantInfo tenantInfo = tenantService.getTenantInfo(tenantId);
if (tenantInfo == null) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "租户不存在");
return ResponseEntity.badRequest().body(response);
}

// 4. 设置租户上下文
TenantContext.setTenantId(tenantId);
TenantContext.setTenantInfo(tenantInfo);

Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "租户切换成功");
response.put("tenantInfo", tenantInfo);

return ResponseEntity.ok(response);

} catch (Exception e) {
log.error("切换租户失败: tenantId={}", tenantId, e);

Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "切换租户失败: " + e.getMessage());

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

/**
* 获取租户资源使用情况
*/
@GetMapping("/resources")
public ResponseEntity<Map<String, Object>> getTenantResources() {
try {
String tenantId = TenantContext.getTenantId();
if (tenantId == null) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "租户上下文不存在");
return ResponseEntity.badRequest().body(response);
}

Map<String, ResourceUsage> resourceUsage = resourceManager.getTenantResourceUsage(tenantId);

Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("tenantId", tenantId);
response.put("resourceUsage", resourceUsage);

return ResponseEntity.ok(response);

} catch (Exception e) {
log.error("获取租户资源使用情况失败", e);

Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取租户资源使用情况失败: " + e.getMessage());

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

/**
* 分配租户资源
*/
@PostMapping("/resources/allocate")
public ResponseEntity<Map<String, Object>> allocateTenantResource(
@RequestParam String resourceType,
@RequestParam int resourceCount) {
try {
String tenantId = TenantContext.getTenantId();
if (tenantId == null) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "租户上下文不存在");
return ResponseEntity.badRequest().body(response);
}

boolean allocated = resourceManager.allocateTenantResource(tenantId, resourceType, resourceCount);

Map<String, Object> response = new HashMap<>();
response.put("success", allocated);
response.put("message", allocated ? "资源分配成功" : "资源分配失败");
response.put("tenantId", tenantId);
response.put("resourceType", resourceType);
response.put("resourceCount", resourceCount);

return ResponseEntity.ok(response);

} catch (Exception e) {
log.error("分配租户资源失败: resourceType={}, count={}", resourceType, resourceCount, e);

Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "分配租户资源失败: " + e.getMessage());

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
}

7. 总结

通过多租户支持与资源隔离的实现,我们成功构建了一个企业级多租户架构。关键特性包括:

7.1 核心优势

  1. 多租户架构: 租户识别、租户上下文、租户管理
  2. 资源隔离: 计算资源、存储资源、网络资源隔离
  3. 数据隔离: 数据库级、Schema级、表级隔离
  4. 权限控制: 租户权限、用户权限、资源权限
  5. 租户管理: 租户生命周期、资源分配、监控管理

7.2 最佳实践

  1. 多租户隔离: 多种隔离策略支持、动态数据源路由
  2. 资源管理: 资源池管理、资源限制控制、资源监控
  3. 权限控制: 多层级权限验证、跨租户访问控制
  4. 数据安全: 数据隔离策略、数据访问控制
  5. 系统监控: 租户监控、资源监控、性能监控

这套多租户支持与资源隔离方案不仅能够实现多租户共享基础设施,还包含了完整的资源隔离、数据隔离、权限控制等核心功能,是企业级SaaS平台的重要技术基础。