理解用户、租户与会话上下文
背景
在我们的系统中,一个用户可以拥有多个租户,这带来了几个关于“ID”的核心概念。为了消除混淆,本文档将清晰地定义以下三个关键概念:
- 用户的注册来源 (
users.tenant_id) - 租户的所有权关系 (
tenants.user_id) - 当前管理的租户 (
owned_tenant_id- 运行时变量)
1. users.tenant_id:用户的注册来源 (数据库字段)
这是 users 表中的一个字段,用于标记用户的**“注册来源”。此字段在用户创建时被设定,之后永远不会改变**。
-
官方平台注册:
- 当用户在我们的官方主站注册时,他的
users.tenant_id被设为固定值1。
- 当用户在我们的官方主站注册时,他的
-
租户网站注册:
- 当用户通过特定租户的网站注册时, 他的
users.tenant_id被设为该租户的ID。
- 当用户通过特定租户的网站注册时, 他的
核心用途: 仅用于追踪用户来源,进行数据分析。
2. tenants.user_id:租户的所有权 (数据库字段)
一个用户可以拥有并管理一个或多个租户。这个“所有权”是通过 tenants 表中的 user_id 字段来定义的。
- 定义:
tenants表中的user_id是一个外键,指向users表的主键。 - 一对多关系: 如果一个用户拥有多个租户,那么在
tenants表中就会有多条记录拥有相同的user_id。 - 查询: 要找到一个用户拥有的所有租户,可以执行以下查询:
SELECT id, name FROM tenants WHERE user_id = '<user_id>';
3. owned_tenant_id:当前管理的租户 (运行时变量)
💡 重要提示: 这是最容易引起混淆、但也是最需要理解的概念:
owned_tenant_id不是数据库里的一个字段,而是一个在程序运行时(例如,在用户的登录会话、JWT Token 或 API 请求头中)使用的临时变量。
- 作用: 当一个拥有多个租户的用户登录系统时,他需要选择当前要管理哪一个租户。
owned_tenant_id这个变量就用来存放用户当前选择的租户ID。 - 生命周期: 它在用户选择要管理的租户后被赋值,并在用户退出登录或切换管理租户时被改变或销毁。
完整流程示例:登录与管理
让我们将这三个概念串联起来:
前提:
- 用户
王五已在平 台注册 (users表:{ id: 123, tenant_id: 1 })。 王五已创建了两个租户 (tenants表有两条记录:{ id: "tenant-c", user_id: 123 }和{ id: "tenant-d", user_id: 123 })。
流程:
-
用户登录:
王五使用他的账号密码登录。 -
选择租户:
- 后端通过查询
tenants表 (SELECT * FROM tenants WHERE user_id = 123),发现王五拥有 "tenant-c" 和 "tenant-d" 两个租户。 - 前端界面(UI)向
王五显示一个选择列表:“请选择您要管理的租户:[公司C, 公司D]”。
- 后端通过查询
-
设置管理上下文:
王五点击选择“公司D”。- 前端将用户选择的
tenant-d作为参数,请求后端为此会话设置管理上下文。 - 后端生成一个包含
owned_tenant_id: "tenant-d"的JWT或会话记录。
-
执行管理操作:
王五点击“用户管理”页面,前端发起请求GET /api/manage/users。- 该请求的凭证(JWT)中包含了
{ user_id: 123, owned_tenant_id: "tenant-d" }。 - 后端API接收到请求,从凭证中解析出
owned_tenant_id是 "tenant-d"。 - 后端的所有数据库操作都将基于 "tenant-d" 来执行(例如,
SELECT * FROM users WHERE tenant_id = 'tenant-d'),从而确保他管理的是“公司D”而不是“公司C”的事务。
总结
| 概念 | 位置/方式 | 含义 | 不变性/动态性 |
|---|---|---|---|
tenant_id | users 表的持久化字段 | 用户的注册来源 | 不变 |
| 所有权 | tenants 表的 user_id 字段 | 定义了哪个用户拥有哪个或哪些租户 | 关系固定 |
owned_tenant_id | 会话/JWT等上下文中的临时变量 | 用户当前选择管理的租户 | 动态 |