You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 06:45:28