跳到主要内容
版本:0.4.x

多租户 / Multi-Tenant

Sharkable 提供可选启用的多租户解析系统。通过 Scoped 服务 ITenant 注入到任何需要租户感知的类中,租户标识在每次请求时经由可配置的中间件解析。

快速开始

builder.Services.AddShark(opt =>
{
opt.ConfigureMultiTenant(cfg =>
{
cfg.ResolveTenant = ctx =>
TenantResolver.FromHost(ctx)
?? TenantResolver.FromClaim(ctx);
});
});

var app = builder.Build();
app.UseShark(); // 自动注册 TenantResolutionMiddleware

在任何地方注入 ITenant

public class OrderService
{
private readonly ITenant _tenant;

public OrderService(ITenant tenant)
{
_tenant = tenant;
}

public async Task<List<Order>> GetOrdersAsync()
{
var tenantId = _tenant.TenantId;
// 按 tenantId 做数据隔离
}
}

解析租户

TenantResolutionMiddleware 在每个请求开始时执行租户解析,结果写入当前请求的 Scoped ITenant 实例。

中间件在每个请求开始时检查 TenantOptions.ResolveTenant 委托。通过 ConfigureMultiTenant() 设置。

内置解析器

// 子域名:tenant1.myapp.com → "tenant1"
TenantResolver.FromHost(HttpContext)

// JWT Claim:读取 "tenant_id" claim(可自定义)
TenantResolver.FromClaim(HttpContext, claimType: "tenant_id")

?? 链式组合实现 fallback:

cfg.ResolveTenant = ctx =>
TenantResolver.FromHost(ctx)
?? TenantResolver.FromClaim(ctx, "tenant_id");

自定义委托

支持任意解析策略:

cfg.ResolveTenant = ctx =>
{
var header = ctx.Request.Headers["X-Tenant"].FirstOrDefault();
return !string.IsNullOrEmpty(header) ? header : null;
};

配置

opt.ConfigureMultiTenant(cfg =>
{
// Func<HttpContext, string?> — 无法解析时返回 null
cfg.ResolveTenant = ctx => TenantResolver.FromClaim(ctx);
});

行为

场景结果
解析器返回值本次请求的 ITenant.TenantId 被设置
解析器返回 nullITenant.TenantIdnull;应用自行处理
未调用 ConfigureMultiTenant不会注册中间件,不做租户解析
多个解析器链式组合第一个非 null 结果生效

数据源隔离

通过 ITenantDataSource 按租户路由数据库连接。在任意位置注入即可获取当前请求对应的正确连接串:

builder.Services.AddShark(opt =>
{
opt.ConfigureMultiTenant(cfg =>
{
cfg.ResolveTenant = ctx => TenantResolver.FromHost(ctx);

cfg.ConfigureDataSource(o =>
{
o.ConnectionStringResolver = tenantId =>
$"Server=db-{tenantId}.internal;Database=myapp";
});
});
});

// 任意 Scoped 服务中:
public class OrderService
{
private readonly ITenantDataSource _ds;
public OrderService(ITenantDataSource ds) => _ds = ds;

public void Connect()
{
var connStr = _ds.GetConnectionString(); // 自动按租户解析
}
}

ITenantDataSource 是 Scoped 服务,从 ITenant 获取当前租户,再通过 ConnectionStringResolver 映射为连接串。租户未解析时返回 null

NuGet 插件(如 Sharkable.AutoCrud.SqlSugar)可将 ITenantDataSource 注入其 DB 客户端注册中,让注入的客户端自动指向正确的租户数据库——用户代码零修改。

AOT 支持

完全 AOT 安全。无反射、无 dynamic。中间件通过 Func<HttpContext, string?> 委托解析租户,ITenant / Tenant 均为普通接口和类。