Laravel 8 子域名租户路由需在 RouteServiceProvider 的 mapWebRoutes() 开头用 Route::domain('{tenant}.example.com') 注册,配合 routes/tenant.php 中显式接收 $tenant 参数、Nginx/Apache 泛解析、hosts 逐条映射、动态 APP_URL 及 InitializeTenant 中间件提取租户并切换数据库连接。

你需要让 Laravel 8 应用根据访问的子域名(如 tenant1.example.com、shop.example.com)自动匹配对应租户的路由,同时保证每个租户的请求能正确识别、隔离并加载专属配置——这不能靠拼接字符串或 prefix 模拟,必须从路由注册时机、Host 头解析机制和参数注入路径三处同时入手。
在 RouteServiceProvider 中注册子域名路由组
所有子域名路由必须在 routes/web.php 被加载前 就完成注册,否则会被根域名路由拦截。Laravel 的路由匹配是顺序优先(First Match Wins),而 routes/web.php 是在 RouteServiceProvider 的 mapWebRoutes() 方法中被 require 的,所以你必须把 Route::domain() 写进这个方法里,而不是直接丢进 web.php 文件顶部。
打开 app/Providers/RouteServiceProvider.php,定位到 mapWebRoutes() 方法,在其内部最开头插入:
Route::domain('{tenant}.example.com')->group(function () { require base_path('routes/tenant.php'); });
⚠️ 注意:这里不能写成 Route::domain('*.example.com') —— Laravel 不支持通配符星号语法,必须用 {tenant} 占位符;也不能把这行代码放在 require base_path('routes/web.php') 之后,否则根本不会生效。
创建独立租户路由文件并注入参数
新建 routes/tenant.php 文件,所有租户专属路由都放在这里。关键点在于:{tenant} 是路由参数,不是全局变量,必须显式接收才能使用。
在 tenant.php 中写闭包路由时,函数签名必须带 $tenant 参数:
Route::get('/', function ($tenant) { return "Welcome to {$tenant}'s dashboard"; });
若使用控制器,例如 App\Http\Controllers\Tenant\DashboardController@index,则路由必须声明参数绑定:
Route::get('/', [DashboardController::class, 'index']);
并在控制器方法中接收:
public function index(string $tenant) { // $tenant 值来自子域名,如访问 shop.example.com → $tenant = 'shop' }
不这样做会导致「Missing argument」错误,或在中间件中调用 $request->route('tenant') 返回 null —— 因为参数未被框架注入,只是存在于 route()->parameters() 里但未解包。
配置 Web 服务器泛解析与本地 hosts 映射
Nginx 必须把所有子域名请求转发给 Laravel 入口,否则请求压根到不了 PHP 层。server_name 不能只写 example.com,必须包含通配符:
server_name example.com *.example.com;
Apache 用户需启用 vhost_alias 模块,并在 VirtualHost 中添加:
ServerAlias *.example.com
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
本地开发时,/etc/hosts 必须逐条添加,不能只写一行 127.0.0.1 *.example.com(系统不支持):
127.0.0.1 tenant1.example.com
127.0.0.1 tenant2.example.com
127.0.0.1 admin.example.com
【缺少任一 hosts 条目,浏览器发出的 Host 请求头就是 example.com 而非 tenant1.example.com,Laravel 根本收不到子域名信息】
动态设置 APP_URL 以支持正确生成 URL
硬编码 APP_URL=https://example.com 会让 route()、url()、邮件链接全部指向根域名,租户页面里的所有链接都会失效。
修改 config/app.php 中的 'url' 配置项:
'url' => env('APP_URL', request()->getSchemeAndHttpHost()),
这样每次请求都会读取当前 Host 头生成协议+域名组合,访问 tenant1.example.com 时 url('/') 就输出 https://tenant1.example.com/,而非 https://example.com/。
注意:此写法依赖 request() 辅助函数,因此不能用于 config 文件加载早期(如 database.php 中),仅适用于运行时可访问 Request 实例的上下文。
通过中间件提取租户并切换数据库连接
第一步:创建中间件 php artisan make:middleware InitializeTenant
第二步:在 handle() 方法中获取子域名值:
$tenant = $request->route('tenant');
第三步:查 tenants 表验证租户存在且状态为 active,失败则 abort(404);
第四步:动态设置数据库连接名并强制重建连接:
Config::set('database.connections.tenant.database', "tenant_{$tenant}");
DB::purge('tenant');
第五步:将中间件挂载到子域名路由组:
Route::domain('{tenant}.example.com')->middleware('initialize-tenant')->group(function () { require base_path('routes/tenant.php'); });
【中间件必须挂载在 domain() 分组上,不能挂载在 group() 内部,否则 $request->route('tenant') 在中间件执行时还未解析】


















