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

基本介绍

源码插件通过services.Tenant()消费普通租户能力。租户能力是可选框架能力,官方提供方插件标识为linapro-tenant-core。没有活跃提供方时,服务会降级到平台租户0的单租户语义。

动态插件可声明service: tenant调用已发布的租户能力方法。

能力阶段:运行期

类型支持:源码插件、动态插件

能力设计

SPI模式

Tenant能力采用SPI模式,具体多租户策略由提供方插件实现。tenantcap.Provider负责租户解析、用户租户关系校验、用户可见租户列表和租户切换校验。tenantcap.Resolver负责从HTTP请求解析租户身份,可以按请求头、域名、路径、令牌或其他策略组成责任链。

安全降级

当没有租户提供方时,系统会降级为平台租户的单租户模式。普通Tenant()不会暴露RequestResolverScopeService、用户租户成员关系写入或启动一致性检查等接口,这些属于宿主内部的中间件、数据库过滤或治理流程。

源码插件专属:TenantFilter

源码插件如果需要查询插件自有表,可以通过pluginhost.Services.TenantFilter()获取tenantcap.PluginTableFilterService。它属于租户领域能力,但不属于普通capability.Services.Tenant();原因是它直接接收并返回*gdb.Model查询构建器,只适合运行在宿主进程内的源码插件使用。

方法说明
Context返回当前请求的租户、用户、真实操作者、模拟状态和平台绕过信息
Apply向查询模型追加tenant_id条件;当前请求允许平台绕过时返回原模型

TenantFilterContext包含UserIDTenantIDActingUserIDOnBehalfOfTenantIDActingAsTenantIsImpersonationPlatformBypass。其中ActingUserID适合写入审计记录,PlatformBypass由宿主策略判定,插件不应自行构造。

动态插件不使用TenantFilter()。动态插件访问插件自有表时应声明service: data和授权resources.tables,由宿主data服务执行租户、授权和表命名空间治理。

接口定义

源码插件接口

方法说明
Available判断租户能力是否有可用提供方
Status返回能力状态、活跃提供方和冲突原因
Current返回当前请求租户,缺失时返回平台租户
PlatformBypass判断当前请求是否允许绕过租户过滤
EnsureTenantVisible校验当前用户是否可访问指定租户
ValidateUserInTenant校验指定用户是否属于指定租户
ListUserTenants列出用户可见的活跃租户
SwitchTenant校验租户切换目标是否合法

源码插件专属接口(通过pluginhost.Services.TenantFilter()访问):

方法说明
Context返回当前请求的租户上下文信息
Apply向查询模型追加tenant_id条件

动态插件接口

动态方法说明
capability.available判断租户能力是否有可用提供方
capability.status返回能力状态和活跃提供方
tenants.current返回当前请求租户
tenants.platform_bypass判断是否允许绕过租户过滤
tenants.visible.ensure校验当前用户是否可访问指定租户
users.tenant_membership.validate校验指定用户是否属于指定租户
users.tenants.list列出用户可见的活跃租户
tenants.switch.validate校验租户切换目标是否合法

能力使用

源码插件使用

源码插件通过services.Tenant()访问普通租户能力:

// 检查租户能力是否可用
if !services.Tenant().Available(ctx) {
// 降级处理
return
}

// 获取当前租户
tenant := services.Tenant().Current(ctx)

// 校验租户可见性
err := services.Tenant().EnsureTenantVisible(ctx, targetTenantID)

// 列出用户可见租户
tenants, err := services.Tenant().ListUserTenants(ctx, userID)

源码插件使用TenantFilter()给插件自有表追加租户过滤:

model := g.DB().Model("plugin_record")
model = services.TenantFilter().Apply(ctx, model, "")
result, err := model.Where("status", "active").All()

Apply的第三个参数是表名或别名限定符:

qualifier结果
空字符串使用tenant_id
plugin_record使用plugin_record.tenant_id
r使用r.tenant_id

动态插件使用

动态插件在plugin.yaml中声明tenant服务和授权方法:

hostServices:
- service: tenant
methods:
- tenants.current
- tenants.visible.ensure
- users.tenants.list

tenantnone资源类型,不声明pathstableskeysresources。在动态插件侧使用:

tenantSvc := pluginbridge.Default().Tenant()

// 获取当前租户
tenant := tenantSvc.Current(ctx)

// 校验租户可见性
err := tenantSvc.EnsureTenantVisible(ctx, targetTenantID)

// 列出用户可见租户
tenants, err := tenantSvc.ListUserTenants(ctx, userID)

动态插件访问插件自有表时,应声明service: data和授权resources.tables,由宿主数据服务执行租户边界治理。

设计约束

  • 能力可选。 没有租户提供方时,系统按平台租户单租户模式降级。
  • 查询过滤不在普通服务中。 需要数据库查询构建器的租户范围能力是宿主内部ScopeService或源码插件专属TenantFilter()
  • TenantFilter()只用于插件自有表。 不要用它操作宿主核心表,也不要在插件代码里手写不一致的租户条件。
  • 联合查询要传限定符。 当多个表都包含tenant_id时,应传入表名或别名,避免列名歧义。
  • 租户切换只做校验。 SwitchTenant校验目标合法性,重新签发令牌仍由Auth().Token().SwitchTenant完成。
  • 平台绕过由宿主判定。 插件不应自行构造跨租户访问状态。

相关服务