Skip to content

多租户配置(Multi‑Tenancy)

本页系统性介绍 Ape‑Volo‑Admin 的多租户能力与落地方式:租户解析、两种隔离模式(按 Id 与按库)、ORM 过滤器、实体设计、动态路由到租户库,以及常见陷阱与最佳实践。

你将获得

  • 如何开启多租户与选择隔离模式
  • 租户 Id 从哪里来、如何贯穿请求链
  • Id 隔离与库隔离各自的实体/查询/写入策略
  • SqlSugar 层面的全局租户过滤与动态连接路由
  • 常见边界与安全注意(越权、跨租户事务、缓存键设计等)

1. 开关与模式

配置对象(Options):

csharp
[OptionsSettings]
public class TenantOptions
{
    public bool Enabled { get; set; }
    public TenantType Type { get; set; } // Id 或 Db
}

public enum TenantType
{
    [Display(Name = "Enum.Tenant.Id")]
    Id = 1,
    [Display(Name = "Enum.Tenant.Db")]
    Db = 2
}

JSON 配置示例:

json
{
  "Tenant": {
    "Enabled": true,
    "Type": 2
  }
}

含义:

  • Enabled:总开关。
  • Type:隔离模式,1=按 Id(同库同表,按字段隔离),2=按库(不同租户不同库)。

2. 租户解析(TenantId 来源)

系统通过登录态解析当前用户上下文中的 TenantId(App.HttpUser.TenantId)。常见来源:

  • 登录后 Token 中的租户声明(Claim)
  • 管理端切换租户时写入当前上下文

提示:确保登录中间件正确填充 App.HttpUser,并在无租户或租户无权的情况下及时拦截。

3. 模式一:按 Id 隔离(Row‑Level)

实体实现 ITenantEntity,ORM 自动在查询阶段追加租户过滤;写入阶段自动回填 TenantId

实体示例:

csharp
[SugarTable("test_order")]
public class TestOrder : BaseEntity, ITenantEntity
{
    public int TenantId { get; set; }
}

全局查询过滤器(来自 SqlSugar 扩展):

csharp
private static void ConfiguringTenantFilter(this SqlSugarScopeProvider db)
{
    if (App.HttpUser.IsNotNull() && App.HttpUser.TenantId > 0)
    {
        db.QueryFilter.AddTableFilter<ITenantEntity>(it => it.TenantId == App.HttpUser.TenantId);
    }
}

写入时自动补全(AOP DataExecuting 阶段):

csharp
if (tenant != null && App.HttpUser.TenantId > 0)
{
    if (tenant.TenantId == 0)
    {
        tenant.TenantId = App.HttpUser.TenantId;
    }
}

适用场景:

  • 数据量较小或表结构一致性要求高;
  • 单库维护方便、成本低;
  • 跨租户统计可通过管理员角色在“关闭过滤器”的专用查询里完成(需严格鉴权)。

4. 模式二:按库隔离(Database‑Level)

实体添加标记特性 [MultiDbTenant],仓储在访问该实体时,根据当前 TenantId 动态切换到对应租户库。

实体示例:

csharp
[MultiDbTenant]
[SugarTable("test_order")]
public class TestOrder : BaseEntity
{
    public int TenantId { get; set; }
}

动态路由核心逻辑(示意):

csharp
var useMultiTenant = AppSettings.GetValue<bool>("Tenant", "Enabled");
if (useMultiTenant)
{
    var multiDbTenantAttribute = typeof(TEntity).GetCustomAttribute<MultiDbTenantAttribute>();
    if (multiDbTenantAttribute != null && httpUser.IsNotNull() && httpUser.TenantId > 0)
    {
        var tenants = sqlSugarScope.Queryable<Tenant>().WithCache(86400).ToList();
        var tenant = tenants.FirstOrDefault(x => x.TenantId == httpUser.TenantId);
        if (tenant != null)
        {
            var iTenant = sqlSugarScope.AsTenant();
            if (!iTenant.IsAnyConnection(tenant.ConfigId))
            {
                var conn = tenant.ConnectionString;
                if (tenant.DbType == DbType.Sqlite)
                {
                    conn = "DataSource=" + Path.Combine(AppSettings.ContentRootPath, tenant.ConnectionString);
                }
                iTenant.AddConnection(TenantHelper.GetConnectionConfig(tenant.ConfigId, tenant.DbType.Value, conn));
            }
            SugarClient = iTenant.GetConnectionScope(tenant.ConfigId);
            return;
        }
    }
}

适用场景:

  • 大租户/强隔离诉求(数据/性能/合规);
  • 不同租户可使用不同 DB 类型或连接参数;
  • 支持迁移、弹性扩容、分库分表策略。

5. 缓存与跨库注意

  • 缓存键建议统一加租户前缀:如 tenant:{TenantId}:xxx,避免串租;
  • 队列/定时任务等后台服务同样需要“租户上下文”;
  • 严禁跨租户事务;涉及跨租户统计,建议走离线数仓或只读报表库。

6. 与 SqlSugar AOP 及过滤器的协同

参考《DB 仓储(SqlSugar)》:

  • 软删除过滤器 + 多租户过滤器 + 数据权限过滤器 组合;
  • AOP 写入前补全(CreateBy/UpdateBy/TenantId);
  • 慢 SQL 警告与 MiniProfiler 联动,便于定位某租户的慢查询。

7. 常见问题(FAQ)

  1. 为什么查出来是“别的租户”的数据?
  • 实体是否实现了 ITenantEntity(Id 隔离场景)或添加了 [MultiDbTenant](库隔离场景);
  • App.HttpUser.TenantId 是否为空或被错误覆盖;
  • 是否在管理接口中关闭/绕过了过滤器(请严格权限控制)。
  1. 库隔离下,租户配置如何维护?
  • 通常在“租户表”存储租户的 ConfigId/DbType/ConnectionString,并可缓存(如 WithCache(86400));
  • 连接不存在时动态 AddConnection,并通过 GetConnectionScope 获取客户端。
  1. 多租户与日志库/操作异常日志如何配合?
  • 建议日志写入独立库(LogDataBase),与生产数据解耦;
  • 若需按租户检索日志,可在日志结构中添加 TenantId 字段。
  1. 迁移与初始化(Code‑First/Seed)怎么做?
  • Id 隔离:同库同表,按租户回填数据即可;
  • 库隔离:按租户逐库执行迁移与种子,需配套自动化脚本或后台任务。

8. 最佳实践(建议)

  • 统一“租户解析”入口,保证 TenantId 在请求首个环节就生效;
  • 严格的鉴权/审计:管理员跨租户查询需显式开关,并完整记录操作日志;
  • 缓存/消息/任务全面租户化:从键前缀、Topic 到任务分片均考虑租户维度;
  • 对大租户优先选“库隔离”,为迁移与容量规划留足空间;
  • 与 AOP、数据权限、软删除链路联测,防串租与“数据看不见”。

相关文档:

版权所有 © 2021-2026 ApeVolo-Team