告别重复造轮子!ZRAdmin 多租户实战:基于 .NET 8 + SqlSugar 的 SaaS 架构深度解析

74 阅读11分钟

告别重复造轮子!ZRAdmin 多租户实战:基于 .NET 8 + SqlSugar 的 SaaS 架构深度解析

做过 SaaS 系统的开发者都知道,多租户是绕不开的"硬骨头"。数据怎么隔离?租户怎么管理?套餐怎么设计?今天带你拆解 ZRAdmin(10K+ Star 的开源后台框架)内置的完整多租户方案,看它如何用 DB-per-tenant + 套餐授权 的组合拳,把这些问题一次性解决。


一、为什么你需要关注这个方案?

先说结论:如果你正在用 .NET 做后台系统,且有多租户/SaaS 需求,ZRAdmin 的多租户方案值得你花 10 分钟读完。

原因有三:

  1. 开箱即用——一个配置项 TenantSettings:UseTenant 一键开关,关掉之后系统行为和非多租户模式完全一致,零侵入。
  2. 物理隔离——每个租户独立数据库实例,不是共享表加个 tenantId 字段那种"伪隔离",安全性是实打实的。
  3. 全生命周期覆盖——从租户开通、初始化、停服、续费到注销,套餐管理、菜单授权、用户配额、到期提醒,一条龙。

这不是一个"给你个框架自己搭"的半成品,而是一套经过验证的、可直接投入生产的完整方案。


二、ZRAdmin 是什么?

简单介绍一下背景。ZRAdmin(Admin.Core.ZR)是一款基于 .NET 8 + Vue3/uniapp 前后端分离的通用权限管理后台,Gitee 上 10.7K Star,MIT 开源协议。

技术栈一览:

层面技术选型
后端核心.NET 8.0 + Web API + SqlSugar + Swagger
实时通信SignalR(在线用户状态管理)
任务调度Quartz.NET
缓存内存缓存 + Redis
安全接口限流(IpRateLimit)、数据权限过滤器、SQL 注入防护
前端Vue2.x / Vue3.x / uniapp + Element UI / Ant Design
ORMSqlSugar(支持分库分表、多配置)

项目地址:gitee.com/izory/ZrAdm…

在线体验:demo.izhaorui.cn/vue3(账号 admin/123456)


三、多租户架构:为什么选 DB-per-tenant?

多租户隔离方案通常有三种,各有优劣:

┌─────────────────────────────────────────────────────────────┐
│                    多租户隔离方案对比                          │
├──────────────┬──────────────┬──────────────┬────────────────┤
│              │  共享数据库    │  独立 Schema  │  独立数据库     │
│              │  共享表        │  (Schema-per) │  (DB-per)      │
├──────────────┼──────────────┼──────────────┼────────────────┤
│ 数据隔离性    │  ★★☆☆☆       │  ★★★☆☆       │  ★★★★★        │
│ 实现复杂度    │  ★★★★★       │  ★★★☆☆       │  ★★☆☆☆        │
│ 运维成本      │  ★★☆☆☆       │  ★★★☆☆       │  ★★★★★        │
│ 性能扩展性    │  ★★☆☆☆       │  ★★★☆☆       │  ★★★★★        │
│ 合规性        │  ★★☆☆☆       │  ★★★☆☆       │  ★★★★★        │
└──────────────┴──────────────┴──────────────┴────────────────┘

ZRAdmin 选择了 DB-per-tenant(独立数据库) 方案,核心原因:

1. 数据安全是 SaaS 的生命线

共享表方案中,一个 WHERE tenant_id = ? 漏写就是数据泄露事故。独立数据库从物理层面杜绝了这个问题——租户 A 的代码根本无法访问租户 B 的数据库。

2. 合规需求越来越强

尤其是企业级客户、政府项目,"数据必须物理隔离"几乎是硬性要求。DB-per-tenant 天然满足。

3. SqlSugar 的多配置能力让它变简单

独立数据库方案最大的痛点是"动态路由复杂"。但 SqlSugar 的 SugarScope 提供了多配置能力,可以在运行时动态切换数据库连接,这让 DB-per-tenant 的实现成本大幅降低。


四、架构设计:主库 + 租户库

ZRAdmin 的多租户架构采用 "主库 + 租户库" 的双层数据存储设计:

┌─────────────────────────────────────────────────────────────┐
│                      ZRAdmin 多租户架构                       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    主库 (Master DB)                   │   │
│  │                                                       │   │
│  │   ┌──────────┐  ┌──────────┐  ┌──────────────┐      │   │
│  │   │ 租户信息  │  │ 套餐配置  │  │ 平台菜单/权限  │      │   │
│  │   │ SysTenant │  │ Package  │  │   (共享数据)   │      │   │
│  │   └──────────┘  └──────────┘  └──────────────┘      │   │
│  │                                                       │   │
│  └───────────────────────┬───────────────────────────────┘   │
│                          │                                   │
│          ┌───────────────┼───────────────┐                   │
│          │               │               │                   │
│          ▼               ▼               ▼                   │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐         │
│  │  租户A 数据库  │ │  租户B 数据库  │ │  租户C 数据库  │         │
│  │              │ │              │ │              │         │
│  │ · 用户数据    │ │ · 用户数据    │ │ · 用户数据    │         │
│  │ · 业务数据    │ │ · 业务数据    │ │ · 业务数据    │         │
│  │ · 角色权限    │ │ · 角色权限    │ │ · 角色权限    │         │
│  │ · 操作日志    │ │ · 操作日志    │ │ · 操作日志    │         │
│  └──────────────┘ └──────────────┘ └──────────────┘         │
│                                                             │
│  动态路由:SugarScope 根据当前请求的租户ID → 切换到对应数据库    │
└─────────────────────────────────────────────────────────────┘

设计要点:

  • 主库存放共享数据:租户信息、套餐配置、平台级菜单和权限
  • 租户库存放各自业务数据:用户、角色、业务表、日志等
  • 请求进来后,系统从 Token 中解析出租户 ID,通过 SugarScope 动态路由到对应数据库

这种设计的好处是:共享数据集中管理不冗余,业务数据物理隔离不串味。


五、核心能力详解

5.1 一键开关:零侵入设计

配置文件中一行搞定:

{
  "TenantSettings": {
    "UseTenant": true
  }
}

设为 false 时,整个系统退化为单租户模式,所有行为完全不变。这意味着:

  • 你可以在开发阶段关闭多租户,专注业务开发
  • 上线时打开开关即可启用多租户能力
  • 不需要改任何业务代码

这种"可插拔"的设计思路,非常值得学习。

5.2 租户全生命周期管理

ZRAdmin 把租户的完整生命周期都覆盖了:

创建租户 ──→ 初始化(建库+建表+种子数据)──→ 分配套餐
    │                                          │
    │         ┌────────────────────┐           │
    │         │     运行中(正常服务)│←──────────┘
    │         │          │          │
    │         │     ┌────┴────┐     │
    │         │     ▼         ▼     │
    │         │   续费      停服    │
    │         │     │         │     │
    │         │     ▼         ▼     │
    │         │  恢复服务   重新启用 │
    │         └────────┬───────────┘
    │                  │
    └────────────→ 注销(清理数据)

一键开通是最亮眼的功能——点击创建租户,系统自动完成:

  1. 租户档案录入
  2. 自动创建数据库实例
  3. 执行建表脚本
  4. 写入种子数据(默认管理员、默认角色等)
  5. 分配默认套餐
  6. 激活租户

整个过程无需手动建库建表,真正做到了"开箱即用"。

5.3 套餐体系:灵活的商业化基础

预置了 免费版 和 专业版 两套套餐,同时支持自定义套餐的完整 CRUD。

套餐的核心配置维度:

维度说明示例
用户配额限制租户最大用户数免费版 10 人,专业版 100 人
菜单授权控制租户可用的功能模块免费版不含"数据大屏"
到期时间套餐有效期1 个月 / 1 年

套餐体系让 SaaS 的商业化变得简单——不同客户买不同套餐,功能自动隔离,无需改代码。

5.4 套餐菜单授权:双重权限防护

这是 ZRAdmin 多租户设计中的一个亮点。

权限计算公式:

实际可用菜单 = 角色菜单 ∩ 套餐菜单

也就是说,即使租户管理员给某个角色分配了某菜单,如果该菜单不在租户购买的套餐范围内,用户依然看不到。

双重防护的意义:

  • 第一层:套餐菜单——控制"租户能买什么"(平台级)
  • 第二层:角色菜单——控制"租户内谁能用什么"(租户级)

这种设计在商业逻辑和安全逻辑上都是合理的:平台方通过套餐控制售卖范围,租户管理员在套餐范围内做二次分配。

5.5 用户配额自动校验

按套餐限制租户用户数上限。新增用户或批量导入时,系统自动校验:

// 伪代码示意
if (currentTenant.UserCount >= package.MaxUserCount)
{
    throw new BusinessException("当前套餐用户数已达上限,请升级套餐");
}

这个细节很重要——没有配额管理的 SaaS 不是完整的 SaaS。

5.6 到期管理:自动校验 + 主动提醒

  • 自动校验:每次请求时检查租户是否过期,过期自动停服
  • 到期提醒:支持批量查询即将到期的租户,方便运营团队跟进续费
  • 停服恢复:续费后自动恢复服务,数据不丢失

5.7 平台菜单隔离

平台专属功能(如租户管理、全局菜单管理、套餐配置等)仅主租户(平台管理员)可见,普通租户登录后看不到这些入口。这是物理层面之外的第二道防线——就算数据库路由出 bug,前端也不展示,双保险。


六、技术实现揭秘

6.1 动态数据库路由

核心依赖 SqlSugar 的 SugarScope,它支持在运行时动态添加和切换数据库配置。简化后的原理:

// 1. 定义全局 SqlSugarScope
var sqlSugar = new SqlSugarScope(new ConnectionConfig
{
    ConfigId = "master",
    ConnectionString = masterDbConn,
    DbType = DbType.MySql
});

// 2. 请求进来时,根据租户ID动态切换
public async Task UseTenantDb(long tenantId)
{
    var tenant = await GetTenantInfo(tenantId);
    // 获取租户数据库连接串
    var connStr = tenant.DbConnection;

    // 动态添加租户数据库配置(如果尚未添加)
    if (!sqlSugar.IsAnyConnection(tenantId.ToString()))
    {
        sqlSugar.AddConnection(new ConnectionConfig
        {
            ConfigId = tenantId.ToString(),
            ConnectionString = connStr,
            DbType = DbType.MySql
        });
    }

    // 切换到租户数据库
    sqlSugar.ChangeDatabase(tenantId.ToString());
}

实际实现中会配合 中间件/过滤器 自动完成租户识别和数据库切换,业务代码无感知。

6.2 租户识别流程

HTTP 请求
   │
   ▼
解析 Token → 提取 TenantId
   │
   ▼
查询主库 → 获取租户信息(连接串、状态、套餐)
   │
   ▼
校验租户状态 → 是否停服/过期?
   │
   ▼
切换 SugarScope 到租户库
   │
   ▼
执行业务逻辑(自动路由到租户库)
   │
   ▼
返回响应

整个流程对业务层透明——你的 Service 代码不需要写任何 if (tenantId == xxx) 的判断。


七、实际项目中的应用场景

场景一:企业级 SaaS CRM

一家做销售管理 SaaS 的公司,客户有大型企业和小微企业。

  • 大客户要求数据物理隔离 → DB-per-tenant 天然满足
  • 小客户预算有限 → 共享服务器但独立数据库,成本可控
  • 不同客户买不同功能模块 → 套餐菜单授权自动控制
  • 到期续费管理 → 到期提醒 + 自动停服

场景二:教育行业多校管理

一个教育局管理辖区内多所学校,每所学校是独立租户:

  • 各校数据完全隔离,互不干扰
  • 统一平台管理,一套代码服务所有学校
  • 按学校规模分配套餐(用户配额)
  • 平台管理员可以看到全局数据,学校管理员只能看本校数据

场景三:ISV 软件服务商

为不同行业客户提供定制化管理系统:

  • 每个客户独立数据库,支持独立备份和恢复
  • 客户可以随时导出自己的数据
  • 新客户开通只需几分钟(一键创建)
  • 老客户停用后数据保留,随时可恢复

八、与其他方案对比

对比维度ZRAdmin 多租户共享表方案自研方案
数据隔离物理隔离(独立DB)逻辑隔离(tenantId)取决于实现
实现成本开箱即用需要改造每条SQL从零开发
开关控制一行配置需要大改不可逆
套餐体系内置完整需自研需自研
用户配额自动校验需自研需自研
到期管理内置需自研需自研
维护成本框架升级即可SQL遗漏风险高全靠自己
社区支持10K+ Star 活跃社区无无

九、上手指南

快速开始

# 1. 克隆后端代码
git clone https://gitee.com/izory/ZrAdminNetCore.git

# 2. 克隆前端代码(Vue3 推荐)
git clone https://gitee.com/izory/ZRAdmin-vue.git

# 3. 创建数据库,执行初始化脚本
# 4. 修改后端 appsettings.json 中的数据库连接串
# 5. 打开 TenantSettings:UseTenant = true
# 6. 启动后端 + 前端

官方文档:www.izhaorui.cn/doc/quickst…

体验多租户

  1. 使用 admin 登录系统
  2. 进入「租户管理」页面
  3. 点击「新增租户」,填写租户信息并选择套餐
  4. 系统自动创建租户数据库并初始化数据
  5. 切换到租户管理员账号登录,体验隔离效果

十、总结与思考

ZRAdmin 的多租户方案有几个值得学习的设计哲学:

1. 隔离优先,体验统一

选择了 DB-per-tenant 这种"重"方案,但通过 SugarScope 动态路由让业务代码无感知。隔离是物理的,体验是无缝的。

2. 可插拔设计

UseTenant 一个开关,让多租户成为"可选能力"而非"强制架构"。这对于项目前期的灵活性至关重要——你不知道客户最终是否需要多租户,但框架已经准备好了。

3. 商业化思维

套餐、配额、到期管理……这些不是技术功能,而是商业功能。一个好的后台框架不应该只考虑"怎么实现",还要考虑"怎么卖"。ZRAdmin 在这一点上想得很清楚。

4. 双重防护

套餐菜单 ∩ 角色菜单的设计,在安全性和商业逻辑上都站得住脚。这种"防御性设计"的思路值得借鉴。


适合谁用?

  • 正在用 .NET 做 SaaS 系统的团队
  • 需要多租户能力但不想从零开发的开发者
  • 想学习多租户架构设计的 .NET 开发者
  • 需要快速搭建企业级后台的项目

项目信息

项目地址
后端源码(Gitee)gitee.com/izory/ZrAdm…
后端源码(GitHub)github.com/izhaorui/Zr…
前端 Vue3gitee.com/izory/ZRAdm…
官方文档www.izhaorui.cn/doc
在线体验demo.izhaorui.cn/vue3
开源协议MIT

如果这篇文章对你有帮助,欢迎点赞收藏。 项目是开源的,MIT 协议,商用也没问题。觉得好用的话去 Gitee 给个 Star 支持一下作者,开源不易。

你在多租户架构设计中有遇到过什么坑?欢迎评论区交流。


screenshot-1785933315756.png

screenshot-1785933295157.png

screenshot-1785933379221.png

screenshot-1785933351417.png