Laravel 12 用子域名区分租户需在 RouteServiceProvider 中通过 Route::domain('{tenant}.example.com') 注册路由,配合 InitializeTenant 中间件动态切换数据库、配置等,并利用全局作用域自动添加 tenant_id 约束。

用子域名区分多个租户,核心是让同一个 Laravel 12 实例能根据请求的 Host 头(比如 tenant1.example.com、tenant2.example.com)自动识别租户,并隔离数据或配置。这不是靠改路径或加前缀,而是靠 Laravel 原生的 Route::domain() + 中间件动态初始化来完成。
必须在 RouteServiceProvider 中注册子域名路由
不能把 Route::domain() 写在 routes/web.php 里——它必须放在 app/Providers/RouteServiceProvider.php 的 mapWebRoutes() 方法中。否则 {tenant} 占位符无法被解析,中间件也拿不到值。
- 正确写法示例(在
mapWebRoutes()内):
use Illuminate\Support\Facades\Route;
Route::domain('{tenant}.example.com')->group(function () {
require base_path('routes/tenant.php');
});
-
{tenant}会自动作为路由参数注入,后续可在控制器或中间件中通过$request->route('tenant')获取 - 推荐把租户专属路由单独拆到
routes/tenant.php,保持主文件清爽
Web 服务器要支持泛域名解析
Nginx 或 Apache 必须把所有 *.example.com 的请求都转发给这个 Laravel 应用,否则请求根本进不到 PHP 层。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
- Nginx 示例配置片段:
server {
listen 80;
server_name *.example.com;
root /var/www/laravel/public;
# 其他 fastcgi 配置...
}
- 本地开发时,需在
/etc/hosts添加多行记录,如:127.0.0.1 tenant1.example.com127.0.0.1 tenant2.example.com - HTTPS 环境下,务必用泛域名证书(
*.example.com),Let’s Encrypt 支持
用中间件提取租户并初始化上下文
识别出子域名只是第一步。关键是在请求生命周期早期,就根据 tenant 值加载对应租户的数据库、缓存、配置或会话域。
- 创建中间件
InitializeTenant,注册为全局中间件(app/Http/Kernel.php的$middleware数组) - 在中间件中做这几件事:
– 查询 tenants 表验证租户是否存在且启用
– 调用 Config::set('database.connections.tenant.database', $dbName) 切换数据库名
– 执行 DB::purge('tenant') 强制重建连接,避免连接复用污染
– 设置 session.domain 为 .example.com(注意开头的点),让登录态跨子域名共享(可选)
– 可选:绑定当前租户到请求对象,如 $request->attributes->set('tenant', $tenant)
模型层自动隔离数据(共享表方案)
如果采用“共享数据库 + tenant_id 字段”方式,需确保所有业务查询自动带上租户约束。
- 给每个租户相关模型添加
BelongsToTenantTrait,利用creating事件自动填充tenant_id - 配合全局作用域(Global Scope),在
boot()中强制添加where('tenant_id', tenant()->id) - 避免在模型里硬编码
$connection = 'tenant',而应由中间件统一设置连接配置 - 控制器中查数据时,无需手动加
where('tenant_id', ...),框架已自动处理










