Laravel多租户应用:如何追踪会话结束时间及使用会话事件
Laravel会话结束状态追踪与事件使用方案
一、Laravel内置会话事件说明
Laravel提供了两个核心会话事件,可用于追踪会话结束场景:
Illuminate\Session\Events\SessionExpired:当会话超过配置的lifetime时长无活动时触发,对应会话超时场景。Illuminate\Session\Events\SessionInvalidated:当会话被主动销毁(如用户登出)或因其他原因失效时触发。
注意:设置
expire_on_close => true仅会让客户端会话Cookie在浏览器关闭时失效,但服务器端的会话数据仍会保留至lifetime到期,由Laravel的会话垃圾回收机制清理。
二、事件监听与多租户数据库适配
1. 创建事件监听器
生成对应事件的监听器,以处理会话到期为例:
php artisan make:listener RecordSessionExpired --event=Illuminate\Session\Events\SessionExpired
2. 注册事件监听器
在app/Providers/EventServiceProvider.php的$listen数组中完成注册:
protected $listen = [ \Illuminate\Session\Events\SessionExpired::class => [ \App\Listeners\RecordSessionExpired::class, ], \Illuminate\Session\Events\SessionInvalidated::class => [ \App\Listeners\RecordSessionInvalidated::class, ], ];
3. 编写监听器逻辑(适配多租户)
以RecordSessionExpired为例,需先获取会话关联的用户与租户信息,切换到对应租户的数据库连接后记录会话数据:
namespace App\Listeners; use Illuminate\Session\Events\SessionExpired; use Illuminate\Support\Facades\DB; class RecordSessionExpired { public function handle(SessionExpired $event) { // 从会话中提取用户ID和租户标识(根据你的多租户实现调整) $userId = $event->session->get('user_id'); $tenantId = $event->session->get('tenant_id'); if (!$userId || !$tenantId) { return; } // 切换到租户专属数据库连接(假设连接命名规则为tenant_{$tenantId}) DB::purge("tenant_{$tenantId}"); config()->set("database.connections.tenant_{$tenantId}.database", "tenant_db_{$tenantId}"); DB::reconnect("tenant_{$tenantId}"); // 记录会话到期信息到租户数据库 DB::connection("tenant_{$tenantId}")->table('user_session_logs')->insert([ 'user_id' => $userId, 'end_time' => now(), 'end_type' => 'session_timeout', 'created_at' => now(), ]); } }
三、追踪“浏览器关闭”场景的补充方案
服务器无法直接监听浏览器关闭事件,可通过以下间接方式实现追踪:
1. 记录用户最后活动时间
创建中间件记录用户每次请求的最后活动时间:
// app/Http/Middleware/RecordUserActivity.php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\DB; class RecordUserActivity { public function handle($request, Closure $next) { if ($request->user()) { $tenantId = $request->session()->get('tenant_id'); DB::connection("tenant_{$tenantId}")->table('users') ->where('id', $request->user()->id) ->update(['last_activity_at' => now()]); } return $next($request); } }
将此中间件注册到app/Http/Kernel.php的web中间件组中。
2. 识别浏览器关闭行为
在用户登录时,通过对比上次活动时间与本次登录时间的间隔,判定是否为浏览器关闭导致的会话结束:
// 登录控制器逻辑片段 $userId = $user->id; $tenantId = $user->tenant_id; // 查询用户最新的会话记录 $lastSession = DB::connection("tenant_{$tenantId}")->table('user_session_logs') ->where('user_id', $userId) ->orderBy('end_time', 'desc') ->first(); // 若上次会话未标记为主动登出,且间隔小于会话有效期,则判定为浏览器关闭 if ($lastSession && $lastSession->end_type !== 'logout' && now()->diffInMinutes($lastSession->end_time) < config('session.lifetime')) { DB::connection("tenant_{$tenantId}")->table('user_session_logs') ->where('id', $lastSession->id) ->update([ 'end_type' => 'browser_closed', 'end_time' => $user->last_activity_at ]); } // 记录新会话开始 DB::connection("tenant_{$tenantId}")->table('user_session_logs')->insert([ 'user_id' => $userId, 'start_time' => now(), 'created_at' => now(), ]);
四、会话垃圾回收的注意事项
Laravel会话垃圾回收默认随机触发(概率由config/session.php的lottery配置控制),若需及时清理过期会话并触发事件,可手动调用命令:
php artisan session:gc
可将此命令加入app/Console/Kernel.php的定时任务,定期执行以保证数据及时性。
内容的提问来源于stack exchange,提问作者mr2samawi
相关产品推荐
相关产品推荐

