fundplatform/doc/多租户配置示例.md
zhangjf 10eca3fb35 feat: 实现多租户架构完整能力
## 新增功能

### 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: 负载均衡器单元测试
- 混合模式集成测试脚本
2026-02-19 18:10:16 +08:00

407 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多租户配置示例
## 场景一一库多租户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