## 新增功能 ### 1. 多租户核心组件 - TenantRoutingProperties: 租户路由配置属性 - TenantAwareLoadBalancer: 租户感知负载均衡器 - TenantLineHandlerImpl: MyBatis Plus 租户插件 - TenantIgnoreHelper: 忽略租户过滤工具类 - NacosMetadataConfig: Nacos 元数据自动注册 ### 2. Gateway 租户过滤器 - TenantGatewayFilter: 从 JWT 提取租户信息写入请求头 - 透传 X-Tenant-Id、X-Tenant-Group、X-User-Id、X-Username ### 3. 支持的部署模式 - 一库多租户(SaaS 模式): 通过 tenant_id 字段隔离 - 一库一租户(私有化): 独立服务实例和数据库 - 混合模式: VIP 租户专属实例 + 普通租户共享实例 ### 4. Nacos 3.0 适配 - 所有业务模块添加 username/password 认证配置 - 服务实例自动注册租户标签 ## 问题修复 - #8: FeignClient 硬编码 URL 导致 Nacos 服务发现失效 - #9: Nacos 3.0 客户端缺少 username/password 认证配置 - fund-exp expenseType 字段类型从 Integer 改为 Long ## 测试 - TenantAwareLoadBalancerTest: 负载均衡器单元测试 - 混合模式集成测试脚本
407 lines
9.0 KiB
Markdown
407 lines
9.0 KiB
Markdown
# 多租户配置示例
|
||
|
||
## 场景一:一库多租户(SaaS 模式)
|
||
|
||
### Gateway 配置
|
||
|
||
```yaml
|
||
# fund-gateway/src/main/resources/application.yml
|
||
server:
|
||
port: 8000
|
||
|
||
spring:
|
||
application:
|
||
name: fund-gateway
|
||
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
username: nacos
|
||
password: nacos
|
||
|
||
gateway:
|
||
routes:
|
||
- id: fund-sys
|
||
uri: lb://fund-sys
|
||
predicates:
|
||
- Path=/sys/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
- id: fund-cust
|
||
uri: lb://fund-cust
|
||
predicates:
|
||
- Path=/cust/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
# 不需要启用租户路由,所有租户共享实例
|
||
tenant:
|
||
routing:
|
||
enabled: false
|
||
```
|
||
|
||
### 业务服务配置(所有租户共用)
|
||
|
||
```yaml
|
||
# fund-sys/src/main/resources/application.yml
|
||
server:
|
||
port: 8100
|
||
|
||
spring:
|
||
application:
|
||
name: fund-sys
|
||
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
username: nacos
|
||
password: nacos
|
||
# 不需要特殊元数据配置
|
||
|
||
datasource:
|
||
url: jdbc:mysql://localhost:3306/fund_sys?useUnicode=true&characterEncoding=utf8
|
||
username: root
|
||
password: ${DB_PASSWORD}
|
||
hikari:
|
||
maximum-pool-size: 20
|
||
|
||
mybatis-plus:
|
||
mapper-locations: classpath*:/mapper/**/*.xml
|
||
configuration:
|
||
map-underscore-to-camel-case: true
|
||
|
||
# 不启用租户路由
|
||
tenant:
|
||
routing:
|
||
enabled: false
|
||
```
|
||
|
||
### Service 层租户 ID 处理
|
||
|
||
```java
|
||
@Service
|
||
public class UserServiceImpl implements UserService {
|
||
|
||
@Autowired
|
||
private UserMapper userMapper;
|
||
|
||
@Override
|
||
public void saveUser(User user) {
|
||
// 设置默认租户 ID(如果未提供)
|
||
if (user.getTenantId() == null) {
|
||
Long tenantId = TenantContextHolder.getTenantId();
|
||
if (tenantId == null) {
|
||
tenantId = 1L; // 默认租户
|
||
}
|
||
user.setTenantId(tenantId);
|
||
}
|
||
|
||
userMapper.insert(user);
|
||
}
|
||
|
||
@Override
|
||
public List<User> listUsers() {
|
||
// 自动添加租户过滤条件
|
||
Long tenantId = TenantContextHolder.getTenantId();
|
||
return userMapper.selectList(new LambdaQueryWrapper<User>()
|
||
.eq(User::getTenantId, tenantId));
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 场景二:一库一租户(私有化部署)
|
||
|
||
### Gateway 配置(支持租户路由)
|
||
|
||
```yaml
|
||
# fund-gateway/src/main/resources/application.yml
|
||
server:
|
||
port: 8000
|
||
|
||
spring:
|
||
application:
|
||
name: fund-gateway
|
||
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
username: nacos
|
||
password: nacos
|
||
|
||
gateway:
|
||
routes:
|
||
- id: fund-sys
|
||
uri: lb://fund-sys
|
||
predicates:
|
||
- Path=/sys/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
# 报表服务是共享的
|
||
- id: fund-report
|
||
uri: lb://fund-report
|
||
predicates:
|
||
- Path=/report/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
# 启用租户路由
|
||
tenant:
|
||
routing:
|
||
enabled: true
|
||
tenant-header: X-Tenant-Id
|
||
tenant-group-header: X-Tenant-Group
|
||
group-separator: TENANT_
|
||
default-tenant-id: "1"
|
||
|
||
# 共享服务列表(所有租户共用)
|
||
shared-services:
|
||
- fund-gateway
|
||
- fund-report
|
||
- fund-file
|
||
|
||
# 租户专属配置
|
||
tenant-configs:
|
||
tenant_001:
|
||
tenant-id: tenant_001
|
||
services:
|
||
fund-sys:
|
||
service-name: fund-sys
|
||
port: 8100
|
||
replicas: 2
|
||
fund-cust:
|
||
service-name: fund-cust
|
||
port: 8110
|
||
replicas: 1
|
||
database:
|
||
url: jdbc:mysql://db-tenant001.com:3306/tenant_001_db
|
||
username: t001
|
||
password: ${DB_PASSWORD_TENANT001}
|
||
|
||
tenant_002:
|
||
tenant-id: tenant_002
|
||
services:
|
||
fund-sys:
|
||
service-name: fund-sys
|
||
port: 8101
|
||
replicas: 1
|
||
fund-cust:
|
||
service-name: fund-cust
|
||
port: 8111
|
||
replicas: 1
|
||
database:
|
||
url: jdbc:mysql://db-tenant002.com:3306/tenant_002_db
|
||
username: t002
|
||
password: ${DB_PASSWORD_TENANT002}
|
||
```
|
||
|
||
### 租户专属服务实例配置
|
||
|
||
```yaml
|
||
# fund-sys 租户 001 实例配置
|
||
# fund-sys-tenant001-instance1.yml
|
||
server:
|
||
port: 8100
|
||
|
||
spring:
|
||
application:
|
||
name: fund-sys
|
||
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
username: nacos
|
||
password: nacos
|
||
metadata:
|
||
tenant-id: tenant_001
|
||
tenant-group: TENANT_TENANT_001
|
||
|
||
datasource:
|
||
url: jdbc:mysql://db-tenant001.com:3306/tenant_001_db
|
||
username: t001
|
||
password: ${DB_PASSWORD_TENANT001}
|
||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||
|
||
tenant:
|
||
routing:
|
||
enabled: true
|
||
```
|
||
|
||
```yaml
|
||
# fund-sys 租户 002 实例配置
|
||
# fund-sys-tenant002-instance1.yml
|
||
server:
|
||
port: 8101
|
||
|
||
spring:
|
||
application:
|
||
name: fund-sys
|
||
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
username: nacos
|
||
password: nacos
|
||
metadata:
|
||
tenant-id: tenant_002
|
||
tenant-group: TENANT_TENANT_002
|
||
|
||
datasource:
|
||
url: jdbc:mysql://db-tenant002.com:3306/tenant_002_db
|
||
username: t002
|
||
password: ${DB_PASSWORD_TENANT002}
|
||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||
|
||
tenant:
|
||
routing:
|
||
enabled: true
|
||
```
|
||
|
||
### 启动脚本示例
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
|
||
# 启动租户 001 的系统服务(2 个实例)
|
||
echo "Starting fund-sys for tenant_001 (instance 1)..."
|
||
nohup java -jar fund-sys.jar \
|
||
--spring.cloud.nacos.discovery.metadata.tenant-id=tenant_001 \
|
||
--spring.cloud.nacos.discovery.metadata.tenant-group=TENANT_TENANT_001 \
|
||
--server.port=8100 \
|
||
--spring.datasource.url=jdbc:mysql://db-tenant001.com:3306/tenant_001_db \
|
||
> logs/fund-sys-tenant001-instance1.log 2>&1 &
|
||
|
||
echo "Starting fund-sys for tenant_001 (instance 2)..."
|
||
nohup java -jar fund-sys.jar \
|
||
--spring.cloud.nacos.discovery.metadata.tenant-id=tenant_001 \
|
||
--spring.cloud.nacos.discovery.metadata.tenant-group=TENANT_TENANT_001 \
|
||
--server.port=8101 \
|
||
--spring.datasource.url=jdbc:mysql://db-tenant001.com:3306/tenant_001_db \
|
||
> logs/fund-sys-tenant001-instance2.log 2>&1 &
|
||
|
||
# 启动租户 002 的系统服务(1 个实例)
|
||
echo "Starting fund-sys for tenant_002 (instance 1)..."
|
||
nohup java -jar fund-sys.jar \
|
||
--spring.cloud.nacos.discovery.metadata.tenant-id=tenant_002 \
|
||
--spring.cloud.nacos.discovery.metadata.tenant-group=TENANT_TENANT_002 \
|
||
--server.port=8102 \
|
||
--spring.datasource.url=jdbc:mysql://db-tenant002.com:3306/tenant_002_db \
|
||
> logs/fund-sys-tenant002-instance1.log 2>&1 &
|
||
|
||
# 启动客户服务(共享服务,所有租户共用)
|
||
echo "Starting fund-cust (shared service)..."
|
||
nohup java -jar fund-cust.jar \
|
||
--server.port=8110 \
|
||
--spring.datasource.url=jdbc:mysql://localhost:3306/fund_cust \
|
||
> logs/fund-cust-shared.log 2>&1 &
|
||
|
||
echo "All services started!"
|
||
```
|
||
|
||
---
|
||
|
||
## 场景三:混合模式(部分租户独立部署)
|
||
|
||
```yaml
|
||
# Gateway 配置
|
||
tenant:
|
||
routing:
|
||
enabled: true
|
||
default-tenant-id: "1"
|
||
shared-services:
|
||
- fund-gateway
|
||
- fund-report
|
||
- fund-file
|
||
|
||
# 只有大客户有独立部署
|
||
tenant-configs:
|
||
# VIP 客户:独立部署
|
||
vip_customer:
|
||
tenant-id: vip_customer
|
||
services:
|
||
fund-sys:
|
||
service-name: fund-sys
|
||
port: 8200
|
||
replicas: 3
|
||
fund-cust:
|
||
service-name: fund-cust
|
||
port: 8210
|
||
replicas: 2
|
||
database:
|
||
url: jdbc:mysql://vip-db.com:3306/vip_db
|
||
username: vip
|
||
password: xxx
|
||
|
||
# 中小客户:共享部署(不在此配置,使用默认配置)
|
||
```
|
||
|
||
---
|
||
|
||
## 环境变量配置(生产环境推荐)
|
||
|
||
```bash
|
||
# .env 文件
|
||
# Nacos 配置
|
||
NACOS_SERVER_ADDR=nacos.prod.com:8848
|
||
NACOS_USERNAME=nacos
|
||
NACOS_PASSWORD=${NACOS_PROD_PASSWORD}
|
||
|
||
# 数据库配置
|
||
DB_HOST_PROD=mysql.prod.com
|
||
DB_PORT_PROD=3306
|
||
DB_USERNAME_PROD=root
|
||
DB_PASSWORD_PROD=${DB_PROD_PASSWORD}
|
||
|
||
# 租户配置
|
||
TENANT_ROUTING_ENABLED=true
|
||
TENANT_DEFAULT_ID=1
|
||
|
||
# 租户 001 配置
|
||
TENANT_001_ID=tenant_001
|
||
TENANT_001_DB_HOST=db-tenant001.com
|
||
TENANT_001_DB_USERNAME=t001
|
||
TENANT_001_DB_PASSWORD=${TENANT001_PASSWORD}
|
||
|
||
# 租户 002 配置
|
||
TENANT_002_ID=tenant_002
|
||
TENANT_002_DB_HOST=db-tenant002.com
|
||
TENANT_002_DB_USERNAME=t002
|
||
TENANT_002_DB_PASSWORD=${TENANT002_PASSWORD}
|
||
```
|
||
|
||
```yaml
|
||
# application-prod.yml
|
||
spring:
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: ${NACOS_SERVER_ADDR}
|
||
username: ${NACOS_USERNAME}
|
||
password: ${NACOS_PASSWORD}
|
||
|
||
tenant:
|
||
routing:
|
||
enabled: ${TENANT_ROUTING_ENABLED:false}
|
||
default-tenant-id: ${TENANT_DEFAULT_ID:1}
|
||
tenant-configs:
|
||
tenant_001:
|
||
tenant-id: ${TENANT_001_ID}
|
||
database:
|
||
url: jdbc:mysql://${TENANT_001_DB_HOST}:${DB_PORT_PROD}/tenant_001_db
|
||
username: ${TENANT_001_DB_USERNAME}
|
||
password: ${TENANT_001_DB_PASSWORD}
|
||
```
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**最后更新**: 2026-02-18
|