ContiNew Admin多租户架构揭秘:SaaS级企业应用的完美解决方案

ContiNew Admin多租户架构揭秘:SaaS级企业应用的完美解决方案

【免费下载链接】continew-admin 🔥Almost最佳后端规范🔥页面现代美观,且专注设计与代码细节的高质量多租户中后台管理系统框架。开箱即用,持续迭代优化,持续提供舒适的开发体验。当前采用技术栈:Spring Boot3(Java17)、Vue3 & Arco Design、TS、Vite5 、Sa-Token、MyBatis Plus、Redisson、FastExcel、CosId、JetCache、JustAuth、Crane4j、Spring Doc、Hutool 等。 AI 编程纪元,从 ContiNew & AI 开始优雅编码,让 AI 也“吃点好的”。 【免费下载链接】continew-admin 项目地址: https://gitcode.com/continew/continew-admin

引言:为什么企业需要多租户架构?

在当今数字化转型浪潮中,SaaS(Software as a Service)模式已成为企业级应用的主流。多租户架构作为SaaS系统的核心,能够实现一套代码服务多个客户,显著降低运营成本,提高资源利用率。然而,传统多租户实现往往面临数据隔离、性能优化、租户管理等诸多挑战。

ContiNew Admin作为一款高质量的多租户中后台管理系统框架,通过精心设计的架构和丰富的功能特性,为企业提供了完美的SaaS级解决方案。本文将深入解析其多租户架构的设计理念、技术实现和最佳实践。

架构设计概览

多租户模式对比

模式类型数据隔离级别维护成本扩展性适用场景
独立数据库完全隔离金融、医疗等高安全要求
共享数据库独立Schema逻辑隔离中型企业应用
共享数据库共享Schema应用层隔离通用SaaS应用

ContiNew Admin采用共享数据库共享Schema模式,通过应用层实现数据隔离,在保证安全性的同时最大化资源利用率。

核心架构组件

mermaid

核心技术实现

租户上下文管理

ContiNew Admin通过TenantContextHolderTenantContext实现租户上下文的统一管理:

// 租户上下文持有器
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提供完整的租户生命周期管理:

mermaid

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:

【免费下载链接】continew-admin 🔥Almost最佳后端规范🔥页面现代美观,且专注设计与代码细节的高质量多租户中后台管理系统框架。开箱即用,持续迭代优化,持续提供舒适的开发体验。当前采用技术栈:Spring Boot3(Java17)、Vue3 & Arco Design、TS、Vite5 、Sa-Token、MyBatis Plus、Redisson、FastExcel、CosId、JetCache、JustAuth、Crane4j、Spring Doc、Hutool 等。 AI 编程纪元,从 ContiNew & AI 开始优雅编码,让 AI 也“吃点好的”。 【免费下载链接】continew-admin 项目地址: https://gitcode.com/continew/continew-admin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值