【无标题】

告别重复造轮子!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
ORM SqlSugar(支持分库分表、多配置)

项目地址:https://gitee.com/izory/ZrAdminNetCore

在线体验:http://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 一键开关:零侵入设计

配置文件中一行搞定:

json 复制代码
{
  "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 用户配额自动校验

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

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

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

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

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

5.7 平台菜单隔离

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


六、技术实现揭秘

6.1 动态数据库路由

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

csharp 复制代码
// 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 活跃社区

九、上手指南

快速开始

bash 复制代码
# 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. 启动后端 + 前端

官方文档:https://www.izhaorui.cn/doc/quickstart.html

体验多租户

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

十、总结与思考

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

1. 隔离优先,体验统一

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

2. 可插拔设计

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

3. 商业化思维

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

4. 双重防护

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


适合谁用?

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

项目信息

项目 地址
后端源码(Gitee) https://gitee.com/izory/ZrAdminNetCore
后端源码(GitHub) https://github.com/izhaorui/Zr.Admin.NET
前端 Vue3 https://gitee.com/izory/ZRAdmin-vue
官方文档 https://www.izhaorui.cn/doc
在线体验 http://demo.izhaorui.cn/vue3
开源协议 MIT

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

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


相关推荐
小p1 小时前
nextjs学习9: Next.js 渲染与缓存
前端·后端
宋哥转AI1 小时前
深入理解 AI Agent 03|RAG评估体系:量化检索增强效果,精准定位系统短板
人工智能·后端·agent
Leo2821 小时前
数据库Executor-Selector排查实践
后端
Zane19941 小时前
线程池入门:7 大核心参数与 4 种拒绝策略
java·后端
用户298698530141 小时前
无需安装 Excel,也能轻松将 CSV 转为 XLS 或 XLSX
后端·c#·excel
Gopher_HBo1 小时前
Gin架构总览
后端
BingoGo1 小时前
PHP 8.6 新特性一览
后端·php
大陈AI1 小时前
从“打开就卡“到“秒开“:一次 PHP GD 动态海报生成的性能优化实战
后端
他们叫我秃子1 小时前
前端开发转 Go 全栈(三):从函数到 error,Go 连“失败”都要明确返回
前端·后端·go