ContiNew Admin多租户架构揭秘:SaaS级企业应用的完美解决方案
引言:为什么企业需要多租户架构?
在当今数字化转型浪潮中,SaaS(Software as a Service)模式已成为企业级应用的主流。多租户架构作为SaaS系统的核心,能够实现一套代码服务多个客户,显著降低运营成本,提高资源利用率。然而,传统多租户实现往往面临数据隔离、性能优化、租户管理等诸多挑战。
ContiNew Admin作为一款高质量的多租户中后台管理系统框架,通过精心设计的架构和丰富的功能特性,为企业提供了完美的SaaS级解决方案。本文将深入解析其多租户架构的设计理念、技术实现和最佳实践。
架构设计概览
多租户模式对比
| 模式类型 | 数据隔离级别 | 维护成本 | 扩展性 | 适用场景 |
|---|---|---|---|---|
| 独立数据库 | 完全隔离 | 高 | 低 | 金融、医疗等高安全要求 |
| 共享数据库独立Schema | 逻辑隔离 | 中 | 中 | 中型企业应用 |
| 共享数据库共享Schema | 应用层隔离 | 低 | 高 | 通用SaaS应用 |
ContiNew Admin采用共享数据库共享Schema模式,通过应用层实现数据隔离,在保证安全性的同时最大化资源利用率。
核心架构组件
核心技术实现
租户上下文管理
ContiNew Admin通过TenantContextHolder和TenantContext实现租户上下文的统一管理:
// 租户上下文持有器
public class TenantContextHolder {
private static final ThreadLocal<TenantContext> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setTenantContext(TenantContext context) {
CONTEXT_HOLDER.set(context);
}
public static Long getTenantId() {
TenantContext context = CONTEXT_HOLDER.get();
return context != null ? context.getTenantId() : null;
}
}
租户提供者机制
系统通过TenantProvider接口实现灵活的租户识别策略:
public interface TenantProvider {
TenantContext getByTenantId(String tenantIdAsString, boolean verify);
}
// 默认租户提供者实现
public class DefaultTenantProvider implements TenantProvider {
@Override
public TenantContext getByTenantId(String tenantIdAsString, boolean verify) {
TenantContext context = new TenantContext();
Long defaultTenantId = tenantExtensionProperties.getDefaultTenantId();
context.setTenantId(defaultTenantId);
// 租户识别逻辑
if (StrUtil.isBlank(tenantIdAsString)) {
// 从请求头获取租户编码
HttpServletRequest request = ServletUtils.getRequest();
String tenantCode = request.getHeader(tenantExtensionProperties.getTenantCodeHeader());
if (StrUtil.isNotBlank(tenantCode)) {
Long tenantId = tenantService.getIdByCode(tenantCode);
CheckUtils.throwIfNull(tenantId, "编码为 [%s] 的租户不存在");
context.setTenantId(tenantId);
}
} else {
// 直接使用租户ID
Long tenantId = Long.parseLong(tenantIdAsString);
if (verify) {
tenantService.checkStatus(tenantId);
}
context.setTenantId(tenantId);
}
return context;
}
}
数据隔离实现
MyBatis数据过滤拦截器
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})})
public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
// 获取当前租户ID
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null && !isIgnoreTenant(ms.getId())) {
// 自动添加租户过滤条件
parameter = processParameter(parameter, tenantId);
}
return invocation.proceed();
}
private Object processParameter(Object parameter, Long tenantId) {
if (parameter instanceof Map) {
((Map<String, Object>) parameter).put("tenantId", tenantId);
}
return parameter;
}
}
实体类租户标记
@Data
@TableName("sys_user")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String email;
// 租户ID字段
@TableField("tenant_id")
private Long tenantId;
// 其他字段...
}
功能特性详解
1. 灵活的租户配置管理
ContiNew Admin提供完整的租户生命周期管理:
2. 多维度数据隔离
| 隔离维度 | 实现方式 | 安全级别 |
|---|---|---|
| 数据库级别 | 租户ID字段过滤 | 高 |
| 缓存级别 | 租户前缀键名 | 中 |
| 文件存储 | 租户目录隔离 | 高 |
| 消息队列 | 租户专属队列 | 中 |
3. 动态套餐管理
系统支持灵活的套餐配置,每个租户可以选择不同的功能套餐:
public class PackageService {
/**
* 根据租户ID获取可用功能列表
*/
public List<Menu> getAvailableMenus(Long tenantId) {
// 获取租户套餐
Package tenantPackage = packageMapper.selectByTenantId(tenantId);
// 过滤忽略菜单(系统保留功能)
List<Long> ignoreMenus = tenantExtensionProperties.getIgnoreMenus();
return menuMapper.selectByPackageId(tenantPackage.getId())
.stream()
.filter(menu -> !ignoreMenus.contains(menu.getId()))
.collect(Collectors.toList());
}
}
性能优化策略
1. 缓存优化
@Component
public class TenantCacheService {
@Cacheable(value = "tenant", key = "'info:' + #tenantId")
public Tenant getTenantInfo(Long tenantId) {
return tenantMapper.selectById(tenantId);
}
@Cacheable(value = "tenant:config", key = "#tenantId + ':' + #configKey")
public String getConfig(Long tenantId, String configKey) {
return configMapper.selectByTenantAndKey(tenantId, configKey);
}
}
2. 数据库查询优化
-- 自动添加租户过滤条件
SELECT * FROM sys_user WHERE tenant_id = ? AND status = 1;
-- 联合查询优化
SELECT u.*, t.name as tenant_name
FROM sys_user u
LEFT JOIN sys_tenant t ON u.tenant_id = t.id
WHERE u.tenant_id = ?;
3. 连接池管理
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/continew_admin
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
tenant_001:
url: jdbc:mysql://localhost:3306/tenant_001
username: tenant_001
password: tenant_001_pwd
安全防护机制
1. 租户数据交叉访问防护
@Aspect
@Component
public class TenantSecurityAspect {
@Around("execution(* top.continew.admin..service..*.*(..))")
public Object checkTenantPermission(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 检查方法是否需要租户权限验证
if (method.isAnnotationPresent(TenantPermission.class)) {
Long currentTenantId = TenantContextHolder.getTenantId();
Object[] args = joinPoint.getArgs();
// 验证参数中的租户ID是否匹配当前租户
for (Object arg : args) {
if (arg instanceof BaseEntity) {
Long entityTenantId = ((BaseEntity) arg).getTenantId();
if (!currentTenantId.equals(entityTenantId)) {
throw new SecurityException("跨租户数据访问被拒绝");
}
}
}
}
return joinPoint.proceed();
}
}
2. API访问控制
@RestController
@RequestMapping("/api/tenant")
public class TenantDataController {
@GetMapping("/data")
@SaCheckPermission("tenant:data:query")
public ResponseEntity<?> getTenantData(@RequestParam Long dataId) {
// 自动注入租户上下文
Long tenantId = TenantContextHolder.getTenantId();
// 确保数据属于当前租户
TenantData data = dataService.getById(dataId);
if (!tenantId.equals(data.getTenantId())) {
throw new BusinessException("无权访问该数据");
}
return ResponseEntity.ok(data);
}
}
部署与运维
1. 多环境配置
# application-tenant.yml
tenant:
extension:
tenant-code-header: X-Tenant-Code
default-tenant-id: 0
ignore-menus:
- 1 # 系统管理菜单
- 2 # 租户管理菜单
# 数据库配置
spring:
datasource:
dynamic:
datasource:
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



