在Laravel多租户中,中间件需优先从请求上下文(子域名/路径/Header)提取租户标识,查库后调用$tenant->makeCurrent()绑定,并加异常兜底;必须置于$middlewareGroups['web']最前以确保Session、模型查询等均生效租户上下文。

中间件里怎么拿到当前租户?
Laravel 多租户场景下,中间件是识别和绑定租户的最常用入口。关键不是“写个中间件”,而是确保在 handle() 执行时,租户信息已可靠加载,且能被后续所有逻辑(如模型查询、配置读取)感知。
常见错误是直接在中间件里查数据库又没缓存,或依赖路由参数但没校验是否存在——比如租户子域名 tenant1.example.com 对应的 tenant1 在数据库里已被删除,此时中间件不该静默跳过,而应明确拒绝请求。
- 优先从请求上下文提取租户标识:子域名用
$request->getHost()解析,路径前缀用$request->segment(1),Header 用$request->header('X-Tenant-ID') - 用
Tenant::find($id)查库后,立刻调用$tenant->makeCurrent()(假设你用的是stancl/tenancy或类似包),否则 Eloquent 查询仍走主库 - 务必加异常兜底:
if (! $tenant) { throw new NotFoundHttpException(); },别让空租户流到控制器
中间件顺序错乱导致租户切换失效
租户中间件必须在 StartSession、ShareErrorsFromSession 和绝大多数业务中间件之前执行。否则 Session 数据、验证错误、甚至日志记录都可能写进默认租户的上下文。
典型现象:登录后跳转首页,用户数据正确,但日志里显示操作的是 tenant_default;或者 Nova 后台里看到的资源全是主库数据。
- 在
app/Http/Kernel.php的$middlewareGroups['web']中,把租户中间件(例如InitializeTenancyByDomain::class)移到最前面 - 避免在
$middleware全局数组里注册租户中间件——它只对 API 路由生效,而多租户通常依赖 Web 路由的 session 和 cookie - 如果用了
SubdomainTenant模式,确认APP_URL配置不含子域名,否则 Laravel URL 生成器会出错
模型作用域没自动应用租户隔离?
中间件设好了租户,但 User::all() 还是查出所有租户的用户——说明模型没启用租户作用域。这不是中间件的问题,而是模型层缺失约束。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
根本原因:Laravel 默认不强制租户隔离,必须显式声明。哪怕中间件已切换连接,Eloquent 若没加 whereTenantId() 或没监听 building 事件,照样全量查。
- 在模型里用
protected $tenantColumns = ['tenant_id'];(tenancy包支持)或手动加全局作用域:static::addGlobalScope(new TenantScope); - 检查迁移是否为每张表加了
tenant_id字段,并设了索引:$table->unsignedBigInteger('tenant_id')->index(); - 注意软删除模型:若租户数据需独立回收,
deleted_at字段也得配合租户过滤,否则withTrashed()可能跨租户泄露
Artisan 命令和队列任务怎么继承租户上下文?
Web 请求靠中间件自动初始化租户,但 php artisan schedule:run 或队列任务(如 SendWelcomeEmail)启动时没有 HTTP 上下文,tenant()->current() 会返回 null,导致写入错误库或发错通知。
最容易被忽略的一点:队列任务序列化时不会自动捕获当前租户 ID,反序列化后执行就丢了上下文。
- 命令行中手动指定租户:
php artisan tenant:artisan "tinker" --tenant=acme(tenancy包提供) - 队列任务构造时显式传入
$this->tenantId = $tenant->id;,handle()里再调用Tenant::find($this->tenantId)->makeCurrent() - 避免在
AppServiceProvider::boot()里做任何依赖当前租户的绑定(如bind(UserRepository::class, ...)),因为 CLI 和队列不走这个流程
租户隔离真正的复杂点不在中间件写法,而在「上下文传递的完整性」——HTTP 请求、命令行、队列、甚至 Telescope 日志采集,每个入口都要单独确认租户是否真正生效。漏掉任意一个,数据就可能串租户。










