天猫返利app的多租户架构设计:数据隔离与资源共享方案
天猫返利app的多租户架构设计:数据隔离与资源共享方案
大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!
在天猫返利APP的商业化运营中,多租户架构是支撑“品牌代理、区域合作”模式的核心技术底座——例如为不同品牌代理商提供独立的返利规则配置、用户数据视图和财务报表,同时需共享天猫商品库、支付接口等公共资源。本文将从数据隔离模型、资源共享机制、租户路由实现三个维度,结合代码案例详解天猫返利APP的多租户架构设计。
一、数据隔离模型:混合隔离策略的落地
天猫返利APP的租户数据分为“核心敏感数据”(如租户用户信息、财务流水)和“非敏感共享数据”(如商品基础信息、返利规则模板),采用“schema隔离+字段隔离”的混合方案。
1.1 数据库schema隔离实现(核心敏感数据)
为每个租户创建独立的数据库schema,通过cn.juwatech.datasource.TenantDataSourceRouter
实现动态数据源路由:
package cn.juwatech.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import cn.juwatech.tenant.TenantContextHolder;public class TenantDataSourceRouter extends AbstractRoutingDataSource {// 动态获取当前租户的数据源key@Overrideprotected Object determineCurrentLookupKey() {String tenantId = TenantContextHolder.getTenantId();// 默认租户(公共资源库)return tenantId == null ? "default_tenant" : tenantId;}
}
数据源配置类(初始化租户数据源):
package cn.juwatech.datasource.config;import cn.juwatech.datasource.TenantDataSourceRouter;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@Configuration
public class TenantDataSourceConfig {// 初始化默认数据源(公共资源库)private DataSource createDefaultDataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/default_tenant?useSSL=false");dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setMaximumPoolSize(20);return dataSource;}// 初始化租户1数据源(示例:品牌A租户)private DataSource createTenant1DataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/tenant_1001?useSSL=false");dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setMaximumPoolSize(15);return dataSource;}// 注册动态数据源@Beanpublic DataSource tenantDataSource() {TenantDataSourceRouter dynamicDataSource = new TenantDataSourceRouter();Map<Object, Object> dataSourceMap = new HashMap<>();// 添加默认数据源和租户数据源dataSourceMap.put("default_tenant", createDefaultDataSource());dataSourceMap.put("tenant_1001", createTenant1DataSource());dynamicDataSource.setTargetDataSources(dataSourceMap);dynamicDataSource.setDefaultTargetDataSource(createDefaultDataSource());return dynamicDataSource;}// 配置事务管理器@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
1.2 字段隔离实现(非敏感共享数据)
对于天猫商品库等共享数据,在表中添加tenant_id
字段标识租户归属,通过MyBatis拦截器自动注入租户ID:
package cn.juwatech.mybatis.interceptor;import cn.juwatech.tenant.TenantContextHolder;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.sql.Connection;
import java.util.Properties;@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class TenantSqlInterceptor implements Interceptor {// 需要添加租户过滤的表名前缀private static final String[] TENANT_TABLE_PREFIX = {"rebate_rule_", "tenant_config_"};@Overridepublic Object intercept(Invocation invocation) throws Throwable {String tenantId = TenantContextHolder.getTenantId();if (tenantId == null) {return invocation.proceed();}// 获取StatementHandlerStatementHandler statementHandler = (StatementHandler) invocation.getTarget();MetaObject metaObject = SystemMetaObject.forObject(statementHandler);String sql = (String) metaObject.getValue("delegate.boundSql.sql");// 判断SQL是否操作租户相关表,若是则添加tenant_id条件for (String prefix : TENANT_TABLE_PREFIX) {if (sql.toLowerCase().contains("from " + prefix) || sql.toLowerCase().contains("update " + prefix)) {sql = addTenantFilter(sql, tenantId);metaObject.setValue("delegate.boundSql.sql", sql);break;}}return invocation.proceed();}// 拼接租户过滤条件private String addTenantFilter(String sql, String tenantId) {// 处理SELECT语句(添加WHERE条件)if (sql.toLowerCase().startsWith("select")) {int whereIndex = sql.toLowerCase().indexOf("where");if (whereIndex > 0) {return sql.substring(0, whereIndex + 5) + " tenant_id = '" + tenantId + "' and " + sql.substring(whereIndex + 5);} else {return sql + " where tenant_id = '" + tenantId + "'";}}// 处理UPDATE语句else if (sql.toLowerCase().startsWith("update")) {int whereIndex = sql.toLowerCase().indexOf("where");if (whereIndex > 0) {return sql.substring(0, whereIndex + 5) + " tenant_id = '" + tenantId + "' and " + sql.substring(whereIndex + 5);} else {throw new RuntimeException("Update语句必须包含WHERE条件,租户表:" + sql);}}return sql;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
二、资源共享机制:公共服务的租户适配
天猫返利APP需共享天猫商品接口、支付网关、短信服务等公共资源,设计“租户适配层”实现资源的统一调度与隔离控制。
2.1 商品接口共享与租户权限控制
通过cn.juwatech.goods.service.TenantGoodsService
封装天猫商品接口,根据租户等级控制商品访问权限:
package cn.juwatech.goods.service;import cn.juwatech.goods.client.TmallGoodsClient;
import cn.juwatech.goods.dto.GoodsDTO;
import cn.juwatech.tenant.TenantContextHolder;
import cn.juwatech.tenant.dto.TenantDTO;
import cn.juwatech.tenant.service.TenantService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class TenantGoodsService {@Autowiredprivate TmallGoodsClient tmallGoodsClient;@Autowiredprivate TenantService tenantService;// 租户获取天猫商品列表(带权限过滤)public List<GoodsDTO> getTenantGoodsList(Integer page, Integer size, String categoryId) {String tenantId = TenantContextHolder.getTenantId();// 获取租户信息(含商品访问权限等级)TenantDTO tenant = tenantService.getTenantById(tenantId);// 调用天猫商品接口(传入租户标识,用于天猫侧流量统计)List<GoodsDTO> allGoods = tmallGoodsClient.getGoodsList(page, size, categoryId, tenantId);// 根据租户等级过滤商品(如VIP租户可访问高返利商品)if ("VIP".equals(tenant.getLevel())) {return allGoods;} else {return allGoods.stream().filter(goods -> goods.getRebateRate() <= 0.15) // 普通租户仅显示返利≤15%的商品.toList();}}
}
2.2 配置中心实现租户资源共享
使用Nacos作为配置中心,通过“公共配置+租户个性化配置”实现资源共享,配置类示例:
package cn.juwatech.config;import cn.juwatech.tenant.TenantContextHolder;
import com.alibaba.nacos.api.config.annotation.NacosConfigurationProperties;
import org.springframework.context.annotation.Configuration;@Configuration
@NacosConfigurationProperties(dataId = "rebate_common_config", groupId = "DEFAULT_GROUP", autoRefreshed = true)
public class CommonConfig {// 公共配置:天猫接口超时时间(所有租户共享)private Integer tmallApiTimeout = 3000;// 公共配置:短信发送频率限制(所有租户共享)private Integer smsSendLimitPerHour = 100;// 租户个性化配置:返利结算周期(通过租户ID动态获取)public Integer getRebateSettlementCycle() {String tenantId = TenantContextHolder.getTenantId();// 从Nacos获取租户个性化配置(dataId格式:rebate_tenant_{tenantId}_config)String dataId = "rebate_tenant_" + tenantId + "_config";return cn.juwatech.config.NacosConfigUtil.getConfigProperty(dataId, "rebateSettlementCycle", Integer.class, 7);}// getter/setter省略
}
三、租户路由与上下文管理
3.1 租户上下文持有器(ThreadLocal实现)
package cn.juwatech.tenant;public class TenantContextHolder {private static final ThreadLocal<String> TENANT_CONTEXT = new ThreadLocal<>();// 设置当前租户IDpublic static void setTenantId(String tenantId) {TENANT_CONTEXT.set(tenantId);}// 获取当前租户IDpublic static String getTenantId() {return TENANT_CONTEXT.get();}// 清除租户上下文(防止内存泄漏)public static void clear() {TENANT_CONTEXT.remove();}
}
3.2 接口租户路由拦截器
通过Spring MVC拦截器从请求头中获取租户ID,注入上下文:
package cn.juwatech.web.interceptor;import cn.juwatech.tenant.TenantContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class TenantRouteInterceptor implements HandlerInterceptor {// 租户ID请求头名称private static final String TENANT_ID_HEADER = "X-Tenant-Id";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从请求头获取租户IDString tenantId = request.getHeader(TENANT_ID_HEADER);if (tenantId == null || tenantId.isEmpty()) {response.setStatus(400);response.getWriter().write("Tenant ID is required (header: X-Tenant-Id)");return false;}// 验证租户有效性(从数据库或缓存查询)boolean tenantValid = cn.juwatech.tenant.service.TenantService.checkTenantValid(tenantId);if (!tenantValid) {response.setStatus(403);response.getWriter().write("Invalid Tenant ID");return false;}// 设置租户上下文TenantContextHolder.setTenantId(tenantId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清除租户上下文TenantContextHolder.clear();}
}
拦截器注册:
package cn.juwatech.web.config;import cn.juwatech.web.interceptor.TenantRouteInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 所有接口(除公共接口)都需要租户路由registry.addInterceptor(new TenantRouteInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/public/**");}
}
基于上述架构,天猫返利APP已支撑200+租户同时运营,租户数据隔离率100%,公共资源复用率提升70%,且新增租户时仅需创建schema并配置Nacos参数,实现“分钟级”租户开通。后续可引入租户资源配额管理,进一步优化多租户场景下的资源分配效率。
本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!