tenancy/tenancy 仅提供多租户工具链,隔离需手动配置数据库连接、队列、缓存等;常见问题包括 tenant 连接未配置、迁移失败、队列无租户上下文、Redis 缓存未隔离等。

Composer 引入 tenancy/tenancy 扩展包本身不能解决多租户隔离问题——它只是提供了一套可配置的工具链,实际隔离效果完全取决于你怎么配置数据库连接、中间件、队列、缓存和模型作用域。
tenant:artisan 命令执行失败,提示 Database connection [tenant] not configured
这是最常遇到的启动障碍。tenancy/tenancy 默认不会自动注册 tenant 连接名,必须手动在 config/database.php 中声明:
- 在
'connections'数组里添加一个名为'tenant'的连接配置(通常复用mysql配置,但把'database'设为null或占位符) - 确保
TENANT_DATABASE_CONNECTION=tenant已写入.env - 不要依赖
tenancy/tenancy自动切换连接——它只在识别到当前请求属于某租户后,才通过事件(如TenantIdentified)动态设置DB_DATABASE,因此 Artisan 命令需显式指定租户上下文
使用 Tenant::find(1)->run(...) 执行迁移却报错 Base table or view not found
根本原因是迁移命令未正确加载租户专属的 migration 文件路径,或未在租户数据库中创建对应表结构。关键点:
-
php artisan tenant:migrate是推荐方式;Tenant::find(1)->run(...)仅适用于简单命令,不处理 migration 的依赖顺序和 schema 检查 - 确认
tenants/migrations目录已存在且被tenancy/tenancy的MigrationFinder发现(默认只扫描该路径) - 若用共享数据库 + 字段隔离(如
tenant_id),则不能用tenant:migrate,而应改用全局迁移 + 模型全局作用域(boot()中调用static::addGlobalScope(...))
队列任务中访问不到租户上下文,导致写入了主库或错误租户库
tenancy/tenancy 默认不序列化租户上下文进队列任务,Job 执行时处于“无租户”状态。必须显式传递并恢复:
- 在 dispatch 前用
Tenant::current()->id获取 ID,并作为参数传入 Job 构造函数或dispatch(...)->onQueue(...)的 payload - Job 的
handle()方法开头必须调用Tenant::find($this->tenantId)->makeCurrent(),否则后续所有 Eloquent 操作都走默认连接 - 避免在 Job 中直接使用
Auth::user()或request()—— 它们在队列中不可用,也不带租户信息
Redis 缓存未按租户隔离,A 租户改了数据,B 租户读到脏缓存
tenancy/tenancy 不自动为 Redis 键加租户前缀。你需要手动适配:
- 重写
CacheManager的store()方法,或在config/cache.php中为redis驱动配置'prefix' => fn() => Tenant::current()?->id ?: 'system' - 若用 Laravel 的
Cache::tags(),注意 tag 名称也需包含租户标识,例如Cache::tags(['tenant:'.$tenantId, 'posts']) - Session 驱动为 redis 时同理:必须设置
SESSION_PREFIX=tenant_{id}_并在中间件中动态注入,否则登录态会跨租户泄漏
真正的难点从来不在引入哪个包,而在于每个数据通道(DB、Cache、Queue、Session、Broadcast、Storage)都要单独确认是否已绑定当前租户上下文——漏掉任意一个,隔离就形同虚设。











