JVS-Rules在多租户SaaS系统中的权限与规则隔离设计

摘要

SaaS系统必须保证不同租户的数据与配置严格隔离。规则引擎作为业务决策的核心组件,其租户隔离设计直接影响系统的安全性与可扩展性。本文以JVS-Rules为例,详细阐述其在多租户场景下的规则隔离方案,包括租户ID绑定、元数据隔离、规则执行上下文、性能优化及数据权限控制,并提供架构图与代码示例。

1. 多租户规则隔离的挑战

在多租户SaaS系统中,每个租户可能有完全不同的业务规则(折扣、审批流程、风控阈值)。规则引擎需要满足:

  • 数据隔离:租户A的规则不能被租户B看到或执行。

  • 配置独立:每个租户可独立管理自己的决策表、规则流,互不影响。

  • 性能可扩展:租户数量增加时,规则查找与执行不应线性下降。

  • 安全合规:租户间禁止跨租户数据泄露。

2. JVS-Rules的多租户架构

JVS-Rules采用"共享引擎实例+租户级规则仓库"的架构:

text

复制代码
┌─────────────────────────────────────────────┐
│            JVS-Rules 引擎实例                 │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐    │
│  │ 租户A    │ │ 租户B    │ │ 租户C    │    │
│  │ 规则缓存 │ │ 规则缓存 │ │ 规则缓存 │    │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘    │
│       └────────────┼────────────┘           │
│                    ↓                        │
│            规则仓库(按tenant_id分表/分区)   │
└─────────────────────────────────────────────┘

核心设计点:

  • 租户识别:每个API请求携带租户ID(JWT或Header)。

  • 规则存储 :数据库所有规则表包含tenant_id字段,并建立复合索引。

  • 规则缓存:按租户ID隔离的本地缓存(Caffeine/Redis)。

  • 执行隔离:规则编译后的Rete网络按租户独立存储,避免跨租户影响。

3. 数据库隔离设计

采用共享表+租户ID区分方案(适合租户数量<10000的SaaS):

复制代码
sql

CREATE TABLE decision_table (
    id VARCHAR(32) PRIMARY KEY,
    tenant_id VARCHAR(32) NOT NULL,
    name VARCHAR(128),
    ... 
    INDEX idx_tenant (tenant_id)
);

CREATE TABLE decision_rule (
    id VARCHAR(32) PRIMARY KEY,
    table_id VARCHAR(32),
    tenant_id VARCHAR(32) NOT NULL,
    ...
);

所有查询自动加上WHERE tenant_id = ?条件,通过MyBatis Plus拦截器实现。

4. 规则编译与缓存隔离

复制代码
java

@Component
public class TenantRuleEngine {
    private final Map<String, RuleSet> tenantRuleCache = new ConcurrentHashMap<>();
    
    public List<RuleResult> fire(String tenantId, String tableName, Map<String, Object> facts) {
        RuleSet ruleSet = tenantRuleCache.computeIfAbsent(tenantId + "_" + tableName, 
            key -> loadRuleSet(tenantId, tableName));
        return ruleEngine.fire(ruleSet, facts);
    }
    
    private RuleSet loadRuleSet(String tenantId, String tableName) {
        // 从数据库加载该租户的决策表,编译为RuleSet
        DecisionTable table = decisionTableMapper.selectByTenantAndName(tenantId, tableName);
        return DecisionTableCompiler.compile(table);
    }
}

缓存失效策略:当租户修改规则时,发送MQ消息或直接清除对应租户的缓存条目。

5. 规则执行时的上下文安全

规则脚本中可能引用租户特定的数据源或服务。需要确保脚本只能访问授权资源:

复制代码
groovy

// 规则脚本示例(租户A)
def amount = fact.order.amount
def vipThreshold = tenantConfigService.getConfig("vip_threshold") // 租户级配置
if (amount > vipThreshold) {
    result.discount = 0.2
}
为防止脚本跨租户访问,可在脚本沙箱中注入租户ID校验:

java

// 脚本执行前绑定租户上下文
ThreadLocal<String> currentTenant = new ThreadLocal<>();
currentTenant.set(tenantId);
// 脚本中通过工具类获取租户ID,并校验任何数据库查询都带上该ID

6. 性能优化

优化点 实现方式
规则预编译 租户首次请求时编译并缓存,后续直接使用
租户级缓存隔离 使用Caffeine的CacheBuilder为每个租户独立缓存,避免大租户占满内存
数据库连接池 租户信息通过注解传递,MyBatis插件自动注入租户ID,避免SQL拼接开销
批量规则执行 支持批量事实(Facts)传入,减少网络往返

压力测试(100租户,每租户50条规则,并发50):

  • 命中缓存时:P99延迟<8ms。

  • 缓存未命中需加载编译:P99延迟<50ms。

7. 数据权限的进一步扩展

规则引擎除了规则隔离,还可能需要控制规则执行后返回的数据范围。例如,租户A下的不同部门只能查看本部门的数据。JVS-Rules支持在规则动作中调用数据权限服务,对结果集进行过滤。

8. 总结

JVS-Rules的多租户设计兼顾了隔离性、性能与易用性。通过租户ID绑定、缓存隔离、脚本沙箱等机制,实现了安全、高效的SaaS级规则引擎。对于正在构建SaaS产品的团队,这套方案具有较高的参考价值。

免费体验Demo:https://rules.bctools.cn/

详情了解:JVS官网-快速开发平台|JVS低代码开发平台

标签:#规则引擎 #多租户 #SaaS #JVS-Rules