Laravel 8.x 子域名中间件生效需满足:路由用 Route::domain() 在 RouteServiceProvider 中优先注册,中间件在分组内绑定并用 $request->route('tenant') 获取参数,动态切换数据库需 purge 连接,API 子域名须按 EnsureFrontendRequestsAreStateful→auth:sanctum→throttle:api 顺序配置。

在 Laravel 8.x 中,中间件要在子域名路由中生效,必须满足两个前提:一是子域名路由本身被正确注册并匹配到请求;二是中间件在路由分组内被显式绑定,且执行时机能访问到子域名参数(如 {tenant})。漏掉任一环节,中间件就拿不到参数、不执行逻辑,甚至整个路由都进不去。
子域名路由必须用 Route::domain() 注册,且位置要靠前
不能把 Route::domain('{tenant}.example.com') 写在 routes/web.php 底部,也不能塞进 routes/api.php —— 它必须放在 app/Providers/RouteServiceProvider.php 的 mapWebRoutes() 方法最开头。因为 Laravel 路由是“先匹配先执行”,如果根域名的 Route::get('/') 写在前面,所有子域名请求都会被它吃掉,根本不会走到子域名分组里。
- 正确写法示例(在
mapWebRoutes()开头):
Route::domain('{tenant}.example.com')->group(function () {
require base_path('routes/tenant.php');
});
- 确保 Nginx/Apache 配置了泛解析(如
server_name *.example.com;),本地开发还要在/etc/hosts添加对应行,否则请求压根到不了 Laravel。
中间件必须能取到子域名参数
子域名参数 {tenant} 是路由参数,不是全局变量。中间件只有在路由匹配完成后才运行,所以它能访问该参数的前提是:路由已成功匹配,并且参数已注入框架上下文。
- 在中间件
handle()方法中,用$request->route('tenant')获取值,不要用$_SERVER['HTTP_HOST']手动解析; - 如果返回
null,大概率是路由没注册对、顺序错了,或中间件被提前注册在了$middleware全局数组里(此时路由还没匹配,参数不可用); - 推荐把中间件放在分组内调用,例如:
->middleware(InitializeTenant::class),而不是加到全局中间件堆栈。
中间件里切换数据库或配置,要避免连接复用
多租户场景下,常需根据 $tenant 切换数据库连接。但不能简单调用 DB::connection('tenant')->use($dbName),这会导致连接被复用、事务异常或缓存污染。
- 正确做法是在中间件中:查表确认租户存在 → 用
Config::set()动态覆盖数据库配置 → 调用DB::purge('tenant')强制重建连接; - Session 和 Cookie 域名也要适配:在
config/session.php中设'domain' => '.example.com',让登录态跨子域名共享; - 别在模型里硬编码
protected $connection = 'tenant',应保持默认连接为mysql,仅在请求生命周期内动态切换。
API 子域名中间件顺序不能错
如果是 API 类型子域名(如 api.example.com),中间件顺序直接影响认证和限流是否起效。比如 auth:sanctum 必须在 throttle:api 之后,否则未登录用户也会被限流计数;而 EnsureFrontendRequestsAreStateful 必须在最前,否则 Sanctum 无法从 session 或 token 中读取用户。
- 建议在
app/Http/Kernel.php的$middlewareGroups['api-subdomain']中定义专用组; - 在
routes/api.php中用Route::domain('api.example.com')->middleware('api-subdomain')->group(...)绑定; - 改完务必清缓存:
php artisan route:clear && php artisan config:clear,否则旧路由仍生效。



















