## 新增功能 ### 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: 负载均衡器单元测试 - 混合模式集成测试脚本
9.0 KiB
9.0 KiB
多租户配置示例
场景一:一库多租户(SaaS 模式)
Gateway 配置
# 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
业务服务配置(所有租户共用)
# 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 处理
@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 配置(支持租户路由)
# 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}
租户专属服务实例配置
# 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
# 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
启动脚本示例
#!/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!"
场景三:混合模式(部分租户独立部署)
# 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
# 中小客户:共享部署(不在此配置,使用默认配置)
环境变量配置(生产环境推荐)
# .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}
# 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