tenant_id加全局作用域是最可控易落地的共享数据库隔离方式;需用belongstotenant trait注册全局作用域并自动填充tenant_id,依赖tenantcontext单例管理上下文,严格测试跨租户泄露。

tenant_id 字段加全局作用域,是共享数据库方案里最可控、最容易落地的数据隔离方式。它不依赖外部扩展包,也不需要动态切换连接池,适合中小团队快速上线且长期维护。
如何用全局作用域自动过滤 tenant_id?
核心是让所有租户相关模型(比如 User、Order、Vehicle)在查询时自动加上 where tenant_id = X 条件,且写入时自动填充该字段。
做法很简单:定义一个 BelongsToTenant Trait,在 boot() 中注册全局作用域;再在 creating 事件里检查上下文并赋值 tenant_id。
- 全局作用域必须用
addGlobalScope()注册,不能只靠scopeTenant()手动调用——否则漏掉一次就数据越界 -
tenant_id字段类型要和上下文里存的 ID 类型一致(比如unsignedBigInteger对应int,别用string存 UUID 后又用int查询) - 作用域里别用
$this->tenant_id,而要用TenantContext::id()—— 因为作用域执行时模型实例还没 fully hydrated
为什么中间件里必须用 TenantContext 单例?
因为 Laravel 的请求生命周期里,模型查询可能发生在任意位置:控制器、服务类、队列任务、甚至 Artisan 命令。如果靠 session 或 request 传参,队列里就拿不到上下文;如果靠静态变量乱设,多线程下会串租户。
TenantContext 必须是容器单例,且只允许在中间件中初始化一次:
- 中间件里调用
TenantContext::set($tenantId),之后整个请求周期内都可安全调用TenantContext::id() - 队列任务需显式传递
tenant_id并在 handle() 开头重新 set,不能假设上下文自动延续 - Artisan 命令若涉及租户数据,必须通过
--tenant=xxx参数手动注入,不可依赖当前登录用户
tenant_id 为空时模型创建失败怎么办?
常见错误现象:Integrity constraint violation: 1048 Column 'tenant_id' cannot be null。这不是数据库报错,是 Eloquent 没触发自动填充逻辑。
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
原因通常是:
- 模型没 use
BelongsToTenantTrait,或 Trait 里没绑定creating事件 -
TenantContext::isSet()返回 false,但代码没做 fallback 处理(比如超级管理员操作) - 用了
createMany()或批量插入,Eloquent 事件不触发,得手动 map tenant_id
建议在 Trait 的 creating 回调里加断言:abort_unless(TenantContext::isSet(), 403, 'Missing tenant context'),比静默失败更早暴露问题。
测试跨租户数据泄露比写功能还重要
没有测试,global scope 就只是个愿望。真实踩坑点在于:关联查询、withCount、join、raw 查询、软删除恢复——这些地方容易绕过作用域。
最小可行测试结构:
- 创建租户 A 和 B,各自插入一条
Order - 用 A 租户上下文调用
Order::all(),断言 count === 1 且first()->tenant_id === A->id - 用 A 租户上下文执行
Order::with('user')->get(),确认关联模型也带了 tenant_id 过滤 - 手动绕过作用域试一次:
Order::withoutGlobalScopes()->get(),断言结果包含两个租户数据(证明隔离确实生效)
真正难的是那些“看起来没问题”的地方:比如 DB::table('orders')->where(...)->get() 完全不走 Eloquent,也就不会触发作用域——这种裸 SQL 必须人工 review,或者统一封装成 Repository。










