Skip to content

租户 Tenant

租户是平台里业务数据的隔离边界——同一套部署里,A 公司的设备、位号、数据和 B 公司的彼此看不见。每一条业务记录都带一个 tenantId,平台据它把数据切成互不串台的几份。

租户回答的是"这条数据归谁、谁能看见它"。它不是一个功能、也不是一个角色,而是一道数据围墙:你登录后拿到的令牌绑定了一个 tenantId,之后你创建的设备、采到的位号值、下发的指令,全都自动打上这个标签;按 ID 或批量访问别家租户的记录会被判为不存在、或被剔除。

容易混淆的是租户和主体(principal)、角色。一句话区分:租户管"能碰哪条数据",角色管"能做哪类操作",主体是"谁在操作"。三者正交——你可能有 device:get 权限(角色给的),但去 get 别家租户的设备依然失败(租户拦的)。就像一栋写字楼:门禁卡决定你能进哪一层(租户),职级决定你在自己那层能开哪些会议室(角色),工牌上印的是你本人(主体)。

关键字段

租户 TenantBO(表 dc3_tenant,继承 BaseBOid / remark / 审计字段):

字段类型含义
tenantNameString租户名称(展示用)
tenantCodeString租户唯一编码,登录时用它定位租户;编码为 default 的租户是系统管理员租户
tenantExtTenantExt(JSON)扩展配置,预留字段
enableFlagEnableFlagEnum启用标志,见下

租户不是孤立的:身份"属于哪个租户"由租户成员关系 TenantMembershipBO(表 dc3_tenant_membership)一行一行声明,唯一索引建在 (tenant_id, principal_id)

字段类型含义
tenantIdLong归属的租户
principalIdLong归属的主体(principal)
principalTypePrincipalTypeEnum主体类型:USER / SERVICE_ACCOUNT / SYSTEM
membershipStatusMembershipStatusEnum成员状态:ACTIVE / SUSPENDED / INVITED
joinedTimeLocalDateTime加入时间

一个人可以属于多个租户

因为唯一索引在 (tenant_id, principal_id),同一个 USER 主体可以在多个租户下各有一行成员关系(多租户成员)。登录时由 name + tenant 一起定位是哪一段成员关系。按设计 SERVICE_ACCOUNT 服务账号只属于一个租户。

启用标志 enableFlag

EnableFlagEnum数据库说明
ENABLE0启用
DISABLE1禁用

与其它概念的关系

  • 一切实现了 TenantOwned(提供 getTenantId())的业务实体都归某个租户拥有,是隔离的施加对象。
  • 主体经 dc3_tenant_membership 加入租户;进入租户后再由 RBAC(dc3_role_principal_bind)决定能做哪些操作。详见 鉴权 · 租户 · RBAC

隔离是怎么落实的

租户隔离落在控制器层:取数后比对实体 tenantId 与调用方租户,跨租户访问被判为不存在或被剔除。

  • 控制器层(单条按 ID):查到实体后,BaseController.requireTenant() 比对实体的 tenantId 与调用方租户,不一致(或实体不存在)就抛 NotFoundException,对外返回 404
  • 控制器层(批量)BaseController.filterTenant() 只保留属于本租户的条目,直接剔除别家租户的记录。
  • 库级自动追加 WHERE tenant_id = ?:当前未启用(MybatisPlusConfig 只注册了 PaginationInnerInterceptor),作为统一兜底仍在规划中。

跨租户访问返回 404,不是 403

故意用"不存在"而非"无权限"——避免泄露"某个跨租户资源是否存在"。所以你查不到一台设备时,可能它真不存在,也可能它属于别的租户:对你而言两者无差别。批量查询走 filterTenant(),直接把不属于本租户的条目剔除,而不是报错。

示例

开发环境通常只有一个默认租户,其 tenantCode = default——它同时是系统管理员租户:只有 default 租户里的用户才能创建/删除/修改其它租户(TenantController 显式判定 "default".equals(tenantCode))。

设想 SaaS 部署里再开一个客户租户 tenantCode = acmeacme 的运维 alice 登录后(令牌绑定 acmetenantId)创建设备 泵房-01,这台设备落库时 tenant_id 自动写成 acme。此时 default 租户的管理员即便手握 device:get 权限,按 泵房-01 的 ID 去查,也会因 requireTenant() 比对失败而得到 404——除非他先切换到 acme 租户上下文。alice 反过来也看不到 default 租户的任何数据。

管理 API

租户管理端点在鉴权中心,前缀 /tenant(经网关为 /api/v3/auth/tenant)。非管理员只能操作自己所属的租户:

方法路径说明
POST/tenant/add新增租户(仅 default 租户管理员)
POST/tenant/delete删除租户
POST/tenant/update修改租户
GET/tenant/get_by_id按 ID 查询
GET/tenant/get_by_code按编码查询
POST/tenant/list分页查询

延伸阅读

Released under the AGPL-3.0 License · 基于 AGPL-3.0 协议发布