Laravel 7.2多租户多DB应用 租户子域名无法登录问题求助
登录失败根因定位
登录失败的根因可以通过你已完成的测试结果直接锁定:跳过内置验证规则直接返回固定凭证即可正常登录tenant1子域名,说明后续的登录校验、租户库连接、用户密码匹配逻辑全部正常,问题100%出在ValidatesRequests trait执行内置规则校验的环节,存在数据库连接错位。
具体触发逻辑:
- 你在
TenantProvider的register()方法中实现了租户数据库切换逻辑,这个阶段的执行时机早于Laravel很多底层组件的初始化(包括数据库连接实例初始化、验证规则的查询构造)。 - 很多组件会在
register阶段提前创建mysql连接实例,你在后续修改数据库配置、执行DB::purge()/DB::reconnect()时,已经初始化过的旧连接实例不会被自动替换,导致规则校验执行Rule::exists('users', 'email')查询时,仍然查询默认中央库的users表。 - demo子域名可以正常登录的原因:demo的租户账号在中央库也存在,校验规则可以通过;而tenant1的账号仅存在于对应租户库,中央库查询不到匹配记录,直接触发验证失败,根本不会进入后续的登录流程。
- super-admin路径可以正常登录的原因:super-admin走的是根域名,不触发租户库切换逻辑,全程使用中央库连接,查询逻辑完全一致。
修复步骤
1. 调整租户数据库切换的执行时机
不要在服务Provider的register()/boot()方法中做连接切换,改为通过全局中间件实现切库逻辑,确保切库动作发生在所有底层组件初始化完成之后、所有控制器逻辑和数据库查询执行之前:
- 新建全局中间件
app/Http/Middleware/SetTenantDatabase.php,把你原TenantProvider::register()中所有的切库、连接校验代码全部迁移到中间件的handle()方法中:
public function handle($request, Closure $next) { if (app()->runningInConsole()) { return $next($request); } $host = $request->getHttpHost(); $new_host = explode('.', str_replace('www.', '', $host)); if(count($new_host) == 3 && $new_host[0] != 'dev'){ config(['database.connections.mysql.database' => 'c4_'.$new_host[0].'_app']); DB::purge('mysql'); DB::reconnect('mysql'); try { DB::connection()->getPdo(); if(!DB::connection()->getDatabaseName()){ config(['database.connections.mysql.database' => 'c4_app']); DB::purge('mysql'); DB::reconnect('mysql'); die("Could not find the database OR Subdomain. Please check your configuration."); } } catch (\Exception $e) { config(['database.connections.mysql.database' => 'c4_app']); DB::purge('mysql'); DB::reconnect('mysql'); die("Could not open connection to database server. Please check your configuration OR subdomain."); } } return $next($request); }
- 在
app/Http/Kernel.php的$middleware全局数组中,把这个中间件放在数组最前面,确保请求进来最先执行切库动作。
2. 修正验证规则的连接绑定
在构造Rule::exists规则时,手动绑定当前的mysql连接,避免连接实例缓存导致的查询错位:
// 原规则写法 Rule::exists('users', 'email')->where(function ($query) use ($company, $companies, $client) { // 原有where条件逻辑保持不变 }); // 修改为强制指定连接 Rule::connection('mysql')->exists('users', 'email')->where(function ($query) use ($company, $companies, $client) { // 原有where条件逻辑保持不变 });
3. 校验全局模型的连接配置
你代码中调用的GlobalSetting::first()属于全局配置数据,如果这类数据仅存在于中央库,需要给GlobalSetting模型配置独立的中央库连接,避免切到租户库后出现查询异常,同时防止租户侧修改数据影响全局配置。
内容的提问来源于stack exchange,提问作者TECC1
相关产品推荐
相关产品推荐

