feat: 服务资源配置文件增加多租户混合模式负载均衡配置

## application-docker.yml 更新

### fund-sys
- Nacos 元数据配置:tenant-id, tenant-group
- 租户路由配置:启用租户感知负载均衡
- VIP 租户列表定义
- 监控标签:tenant_id, tenant_group, instance_type
- 日志格式:增加 tenantId MDC

### fund-gateway
- 全局跨域配置
- 多租户路由配置(所有服务的VIP租户列表)
- 监控和日志增强

## TenantRoutingProperties 更新
- 添加 @ConfigurationProperties 支持 YAML 配置绑定
- 新增 vip-tenants 列表属性
- 新增 fallback-to-shared 回退策略属性
- 新增 getVipTenants(), isVipTenant(), isFallbackToShared() 方法
This commit is contained in:
zhangjf 2026-02-19 20:45:03 +08:00
parent 5843cc050e
commit 2c0f2f8952
3 changed files with 196 additions and 21 deletions

View File

@ -3,14 +3,32 @@ package com.fundplatform.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.*;
import java.util.List;
import java.util.Map; /**
import java.util.HashMap; * 多租户路由配置属性
*
* <p>支持从 YAML 配置文件读取租户路由配置</p>
*
* <p>配置示例</p>
* <pre>
* tenant:
* routing:
* enabled: true
* default-tenant-id: 1
* services:
* fund-sys:
* vip-tenants:
* - TENANT_VIP_001
* fallback-to-shared: true
* </pre>
*/
@Component
@ConfigurationProperties(prefix = "tenant.routing")
public class TenantRoutingProperties { public class TenantRoutingProperties {
/** 是否启用租户路由 */ /** 是否启用租户路由 */
private boolean enabled = false; private boolean enabled = true;
/** 租户 ID 请求头 */ /** 租户 ID 请求头 */
private String tenantHeader = "X-Tenant-Id"; private String tenantHeader = "X-Tenant-Id";
@ -21,6 +39,9 @@ public class TenantRoutingProperties {
/** 服务组分隔符 */ /** 服务组分隔符 */
private String groupSeparator = "TENANT_"; private String groupSeparator = "TENANT_";
/** 默认租户 ID当未指定时使用 */
private String defaultTenantId = "1";
/** 共享服务列表(不区分租户,所有租户共用) */ /** 共享服务列表(不区分租户,所有租户共用) */
private List<String> sharedServices = Arrays.asList( private List<String> sharedServices = Arrays.asList(
"fund-gateway", "fund-gateway",
@ -28,10 +49,10 @@ public class TenantRoutingProperties {
"fund-file" "fund-file"
); );
/** 默认租户 ID当未指定时使用 */
private String defaultTenantId = "1";
/** 租户服务配置映射 */ /** 租户服务配置映射 */
private Map<String, TenantServiceConfig> services = new HashMap<>();
/** 租户服务配置(旧版兼容) */
private Map<String, TenantServiceConfig> tenantConfigs = new HashMap<>(); private Map<String, TenantServiceConfig> tenantConfigs = new HashMap<>();
// Getters and Setters // Getters and Setters
@ -68,6 +89,14 @@ public class TenantRoutingProperties {
this.groupSeparator = groupSeparator; this.groupSeparator = groupSeparator;
} }
public String getDefaultTenantId() {
return defaultTenantId;
}
public void setDefaultTenantId(String defaultTenantId) {
this.defaultTenantId = defaultTenantId;
}
public List<String> getSharedServices() { public List<String> getSharedServices() {
return sharedServices; return sharedServices;
} }
@ -76,12 +105,12 @@ public class TenantRoutingProperties {
this.sharedServices = sharedServices; this.sharedServices = sharedServices;
} }
public String getDefaultTenantId() { public Map<String, TenantServiceConfig> getServices() {
return defaultTenantId; return services;
} }
public void setDefaultTenantId(String defaultTenantId) { public void setServices(Map<String, TenantServiceConfig> services) {
this.defaultTenantId = defaultTenantId; this.services = services;
} }
public Map<String, TenantServiceConfig> getTenantConfigs() { public Map<String, TenantServiceConfig> getTenantConfigs() {
@ -109,21 +138,76 @@ public class TenantRoutingProperties {
return sharedServices.contains(serviceName); return sharedServices.contains(serviceName);
} }
/**
* 获取服务的 VIP 租户列表
*/
public List<String> getVipTenants(String serviceName) {
TenantServiceConfig config = services.get(serviceName);
if (config != null && config.getVipTenants() != null) {
return config.getVipTenants();
}
return Collections.emptyList();
}
/**
* 判断租户是否为某个服务的 VIP 租户
*/
public boolean isVipTenant(String serviceName, String tenantGroup) {
if (tenantGroup == null || tenantGroup.isEmpty()) {
return false;
}
List<String> vipTenants = getVipTenants(serviceName);
return vipTenants.contains(tenantGroup);
}
/**
* 判断服务是否启用了共享实例回退
*/
public boolean isFallbackToShared(String serviceName) {
TenantServiceConfig config = services.get(serviceName);
if (config != null) {
return config.isFallbackToShared();
}
return true; // 默认启用回退
}
/** /**
* 租户服务配置 * 租户服务配置
*/ */
public static class TenantServiceConfig { public static class TenantServiceConfig {
/** VIP 租户列表(优先路由到专属实例) */
private List<String> vipTenants = new ArrayList<>();
/** 是否回退到共享实例 */
private boolean fallbackToShared = true;
/** 租户 ID */ /** 租户 ID */
private String tenantId; private String tenantId;
/** 服务实例配置 */ /** 服务实例配置 */
private Map<String, ServiceInstanceConfig> services = new HashMap<>(); private Map<String, ServiceInstanceConfig> instances = new HashMap<>();
/** 数据库配置(一库一租户模式) */ /** 数据库配置(一库一租户模式) */
private DatabaseConfig database; private DatabaseConfig database;
// Getters and Setters // Getters and Setters
public List<String> getVipTenants() {
return vipTenants;
}
public void setVipTenants(List<String> vipTenants) {
this.vipTenants = vipTenants != null ? vipTenants : new ArrayList<>();
}
public boolean isFallbackToShared() {
return fallbackToShared;
}
public void setFallbackToShared(boolean fallbackToShared) {
this.fallbackToShared = fallbackToShared;
}
public String getTenantId() { public String getTenantId() {
return tenantId; return tenantId;
} }
@ -132,12 +216,12 @@ public class TenantRoutingProperties {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
public Map<String, ServiceInstanceConfig> getServices() { public Map<String, ServiceInstanceConfig> getInstances() {
return services; return instances;
} }
public void setServices(Map<String, ServiceInstanceConfig> services) { public void setInstances(Map<String, ServiceInstanceConfig> instances) {
this.services = services; this.instances = instances;
} }
public DatabaseConfig getDatabase() { public DatabaseConfig getDatabase() {

View File

@ -17,12 +17,22 @@ spring:
namespace: ${NACOS_NAMESPACE:} namespace: ${NACOS_NAMESPACE:}
group: DEFAULT_GROUP group: DEFAULT_GROUP
enabled: true enabled: true
metadata:
service-type: gateway
gateway: gateway:
discovery: discovery:
locator: locator:
enabled: true enabled: true
lower-case-service-id: true lower-case-service-id: true
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
routes: routes:
- id: fund-sys - id: fund-sys
uri: lb://fund-sys uri: lb://fund-sys
@ -30,6 +40,7 @@ spring:
- Path=/api/sys/** - Path=/api/sys/**
filters: filters:
- StripPrefix=1 - StripPrefix=1
# 租户感知负载均衡(自动添加 tenant-group 请求头)
- id: fund-cust - id: fund-cust
uri: lb://fund-cust uri: lb://fund-cust
predicates: predicates:
@ -75,8 +86,46 @@ spring:
# JWT 配置 # JWT 配置
jwt: jwt:
secret: YourSecretKeyForJWTTokenGenerationMustBeAtLeast256BitsLong secret: ${JWT_SECRET:YourSecretKeyForJWTTokenGenerationMustBeAtLeast256BitsLong}
expiration: 86400000 expiration: ${JWT_EXPIRATION:86400000}
# ==================== 多租户混合模式配置 ====================
tenant:
routing:
# 启用租户感知负载均衡
enabled: true
# 默认租户ID
default-tenant-id: 1
# 租户服务配置定义每个服务的VIP租户
services:
fund-sys:
vip-tenants:
- TENANT_VIP_001
- TENANT_VIP_002
fallback-to-shared: true
fund-cust:
vip-tenants:
- TENANT_VIP_001
fallback-to-shared: true
fund-proj:
vip-tenants:
- TENANT_VIP_001
fallback-to-shared: true
fund-req:
vip-tenants: []
fallback-to-shared: true
fund-exp:
vip-tenants: []
fallback-to-shared: true
fund-receipt:
vip-tenants: []
fallback-to-shared: true
fund-report:
vip-tenants: []
fallback-to-shared: true
fund-file:
vip-tenants: []
fallback-to-shared: true
# Actuator 监控端点配置 # Actuator 监控端点配置
management: management:
@ -98,6 +147,7 @@ management:
metrics: metrics:
tags: tags:
application: ${spring.application.name} application: ${spring.application.name}
service-type: gateway
distribution: distribution:
percentiles-histogram: percentiles-histogram:
http.server.requests: true http.server.requests: true
@ -109,5 +159,8 @@ logging:
level: level:
root: INFO root: INFO
com.fundplatform: DEBUG com.fundplatform: DEBUG
# 多租户负载均衡日志
com.fundplatform.common.loadbalancer: DEBUG
com.fundplatform.gateway.filter: DEBUG
pattern: pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n" console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{tenantId}] %-5level %logger{36} - %msg%n"

View File

@ -38,6 +38,12 @@ spring:
namespace: ${NACOS_NAMESPACE:} namespace: ${NACOS_NAMESPACE:}
group: DEFAULT_GROUP group: DEFAULT_GROUP
enabled: true enabled: true
# 多租户元数据配置(支持混合模式负载均衡)
metadata:
tenant-id: ${TENANT_ID:1}
tenant-group: ${TENANT_GROUP:}
# 服务实例权重VIP实例可配置更高权重
weight: ${NACOS_WEIGHT:1}
# MyBatis Plus 配置 # MyBatis Plus 配置
mybatis-plus: mybatis-plus:
@ -49,6 +55,31 @@ mybatis-plus:
logic-delete-value: 1 logic-delete-value: 1
logic-not-delete-value: 0 logic-not-delete-value: 0
# ==================== 多租户混合模式配置 ====================
tenant:
routing:
# 启用租户感知负载均衡
enabled: true
# 默认租户ID
default-tenant-id: 1
# 租户服务配置
services:
fund-sys:
# VIP租户列表优先路由到专属实例
vip-tenants:
- TENANT_VIP_001
- TENANT_VIP_002
# 共享实例回退策略:当无专属实例时回退到共享实例
fallback-to-shared: true
fund-cust:
vip-tenants:
- TENANT_VIP_001
fallback-to-shared: true
fund-proj:
vip-tenants:
- TENANT_VIP_001
fallback-to-shared: true
# Actuator 监控端点配置 # Actuator 监控端点配置
management: management:
endpoints: endpoints:
@ -69,6 +100,10 @@ management:
metrics: metrics:
tags: tags:
application: ${spring.application.name} application: ${spring.application.name}
# 多租户监控标签
tenant_id: ${TENANT_ID:1}
tenant_group: ${TENANT_GROUP:shared}
instance_type: ${TENANT_GROUP:shared}
distribution: distribution:
percentiles-histogram: percentiles-histogram:
http.server.requests: true http.server.requests: true
@ -80,5 +115,8 @@ logging:
level: level:
root: INFO root: INFO
com.fundplatform: DEBUG com.fundplatform: DEBUG
# 多租户负载均衡日志
com.fundplatform.common.loadbalancer: DEBUG
com.fundplatform.common.nacos: DEBUG
pattern: pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n" console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{tenantId}] %-5level %logger{36} - %msg%n"