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

基本介绍

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

Tenant能力采用子服务模式,聚合Context()(当前租户上下文)、Directory()(租户目录)、Membership()(用户租户成员关系)、Plugins()(租户插件治理)和Filter()(租户过滤上下文)。

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

能力阶段:运行期

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

能力设计

SPI模式

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

子服务体系

Tenant能力采用子服务模式,分别处理不同租户领域操作:

安全降级

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

接口定义

源码插件接口

Tenant()根方法:

方法说明
Available判断租户能力是否有可用提供方
Status返回能力状态、活跃提供方和冲突原因
Context()返回当前租户上下文子服务
Directory()返回租户目录子服务
Membership()返回用户租户成员关系子服务
Plugins()返回租户插件治理子服务
Filter()返回租户过滤上下文子服务

Tenant().Context()子服务:

方法说明
Current返回当前请求租户标识,缺失时返回平台租户
Info返回当前请求租户信息,包含IDCodeNameStatus
PlatformBypass判断当前请求是否允许绕过租户过滤

Tenant().Directory()子服务:

方法说明
Get读取单个可见租户信息
BatchGet批量读取可见租户信息,返回BatchResult
List按关键词搜索可见租户候选
EnsureVisible校验当前用户是否可访问指定租户集合

Tenant().Membership()子服务:

方法说明
ListByUser列出用户可见的活跃租户
Validate校验指定用户是否属于指定租户

Tenant().Plugins()子服务:

方法说明
SetTenantPluginEnabled更新租户插件启用状态,经过调用方和租户策略校验
ProvisionTenantPluginDefaults为租户创建缺失的默认插件行

Tenant().Filter()子服务(源码插件专属):

方法说明
Context返回当前请求的租户、用户、真实操作者、模拟状态和平台绕过信息

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

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

动态插件接口

动态方法说明
capability.available判断租户能力是否有可用提供方
capability.status返回能力状态和活跃提供方
tenants.current返回当前请求租户标识
tenants.current_info返回当前请求租户信息
tenants.platform_bypass判断是否允许绕过租户过滤
tenants.visible.ensure校验当前用户是否可访问指定租户
tenants.batch_get批量读取可见租户信息
tenants.search按关键词搜索可见租户候选
tenants.visible.batch_ensure批量校验当前用户可访问指定租户
users.tenant_membership.validate校验指定用户是否属于指定租户
users.tenants.list列出用户可见的活跃租户
users.tenants.batch_list批量读取用户可访问租户列表
tenants.switch.validate校验租户切换目标是否合法

能力使用

源码插件使用

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

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

// 获取当前租户标识
tenantID := services.Tenant().Context().Current(ctx)

// 获取当前租户信息
tenantInfo, err := services.Tenant().Context().Info(ctx)

// 判断平台绕过
bypass := services.Tenant().Context().PlatformBypass(ctx)

// 校验租户可见性
err := services.Tenant().Directory().EnsureVisible(ctx, []tenantcap.TenantID{targetTenantID})

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

// 批量读取租户信息
batchResult, err := services.Tenant().Directory().BatchGet(ctx, tenantIDs)

// 搜索租户候选
page, err := services.Tenant().Directory().List(ctx, tenantcap.ListInput{
Keyword: "科技",
Page: pageRequest,
})

// 校验用户租户成员关系
err := services.Tenant().Membership().Validate(ctx, userID, targetTenantID)

源码插件通过Filter().Context()获取租户过滤上下文,用于自行构建查询条件:

filterCtx := services.Tenant().Filter().Context(ctx)
if filterCtx.TenantID > 0 {
model = model.Where("tenant_id", filterCtx.TenantID)
}
if filterCtx.IsImpersonation {
// 记录模拟登录审计
log.Infof("模拟用户 %d 访问租户 %d", filterCtx.ActingUserID, filterCtx.TenantID)
}

动态插件使用

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

hostServices:
- service: tenant
methods:
- tenants.current
- tenants.current_info
- tenants.visible.ensure
- tenants.batch_get
- tenants.search
- users.tenants.list
- users.tenants.batch_list

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

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

// 获取当前租户标识
tenantID := tenantSvc.Context().Current(ctx)

// 获取当前租户信息
tenantInfo, err := tenantSvc.Context().Info(ctx)

// 校验租户可见性
err := tenantSvc.Directory().EnsureVisible(ctx, []tenantcap.TenantID{targetTenantID})

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

// 批量读取租户信息
batchResult, err := tenantSvc.Directory().BatchGet(ctx, tenantIDs)

// 搜索租户候选
page, err := tenantSvc.Directory().List(ctx, tenantcap.ListInput{
Keyword: "科技",
Page: pageRequest,
})

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

设计约束

  • 能力可选。 没有租户提供方时,系统按平台租户单租户模式降级。
  • 查询过滤不在普通服务中。 需要数据库查询构建器的租户范围能力是宿主内部ScopeService
  • Filter().Context()返回只读上下文。 插件根据返回的TenantFilterContext自行构建查询条件,不要用它操作宿主核心表。
  • 租户切换只做校验。 Membership().Validate校验目标合法性,重新签发令牌仍由Auth().Token().SwitchTenant完成。
  • 平台绕过由宿主判定。 插件不应自行构造跨租户访问状态。

相关服务