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

Laravel会话超时自动更新用户在线状态的实现方案

需求描述

无论浏览器是否处于打开状态,都需要在会话过期时执行特定操作。在我的Laravel项目中,用户登录时数据库会将其在线状态标记为1,退出时标记为0;我希望会话过期时,无需用户刷新页面,自动将状态更新为0,即使用户未注销直接关闭浏览器,也能在会话过期时标记为离线。

解决方案

我通过分析Laravel会话文件的结构,针对不同场景实现了需求:

场景1:用户登录后无操作超时

当用户登录后15分钟未进行任何操作时,自动跳转至登出路由并返回登录页。我通过JavaScript实现该功能,在所有页面的基础blade模板中添加以下代码:

...
<body>
...

  <form id="logoutForm" method="POST" action="{{ route('logout') }}">
        @csrf <!-- Token CSRF -->
  </form>

...
</body>
...

<script type="text/javascript">
    // 闲置用户自动登出并重定向到登录页脚本
    // 设置闲置超时时间(毫秒)
    var inactivityTimeout = 15 * 60 * 1000; // 15分钟

    // 存储最后一次用户交互时间
    var lastInteractionTime = new Date().getTime();

    // 检查闲置状态,超时则执行登出
    function checkInactivity() {
        var currentTime = new Date().getTime();
        var elapsedTime = currentTime - lastInteractionTime;

        if (elapsedTime >= inactivityTimeout) {
            $('#logoutForm').submit();
        }
    }

    // 更新最后一次交互时间
    function updateLastInteractionTime() {
        lastInteractionTime = new Date().getTime();
    }

    // 监听用户交互事件(移动鼠标、按键、点击)
    $(document).on('mousemove keydown click', updateLastInteractionTime);

    // 每秒检查一次闲置状态
    setInterval(checkInactivity, 1000);
</script>

场景2:用户未注销直接关闭浏览器

根据config/session.php和.env中的配置,用户未注销关闭浏览器后,会话仍会保持1小时活跃:

config/session.php

'lifetime' => env('SESSION_LIFETIME', 60),
'expire_on_close' => false,

.env

SESSION_LIFETIME=60

为了终止这类会话,我创建了Laravel命令UserSessionCleanup:
执行命令:php artisan make:command UserSessionCleanup

在生成的/app/Console/Commands/UserSessionCleanup.php文件中编写如下脚本:

<?php

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Schema;

class UserSessionCleanup extends Command
{
    /**
     * 命令名称与签名
     *
     * @var string
     */
    protected $signature = 'UserSessionCleanup';

    /**
     * 命令描述
     *
     * @var string
     */
    protected $description = "管理用户会话,将过期会话用户的在线状态更新为离线,并删除过期会话文件";

    /**
     * 执行命令
     *
     * @return int
     */
    public function handle()
    {
        $sessionPath = storage_path('framework/sessions');
        $allSessions = [];

        if (is_dir($sessionPath)) {
            $sessionFiles = scandir($sessionPath);

            // 步骤1:收集所有会话信息
            foreach ($sessionFiles as $file) {
                if ($file !== '.' && $file !== '..') {
                    $filePath = $sessionPath . '/' . $file;
                    $fileContent = file_get_contents($filePath);

                    $pattern = '/"login_web_[a-f0-9]+";i:(\d+)/';
                    preg_match($pattern, $fileContent, $matches);

                    if (!empty($matches)) {
                        $userId = $matches[1];
                        $creationTime = filectime($filePath);
                        $formattedTime = date('Y-m-d H:i:s', $creationTime);

                        $allSessions[] = [
                            'user_id' => $userId,
                            'date_file' => $formattedTime,
                            'file_name' => $file,
                        ];
                    }
                }
            }

            // 步骤2:筛选每个用户的最新会话
            $latestSessions = [];
            foreach ($allSessions as $session) {
                $userId = $session['user_id'];
                if (!isset($latestSessions[$userId]) || $session['date_file'] > $latestSessions[$userId]['date_file']) {
                    $latestSessions[$userId] = $session;
                }
            }

            // 步骤3:筛选创建时间超过15分钟的会话
            $filteredSessions = [];
            foreach ($latestSessions as $session) {
                $creationTimestamp = strtotime($session['date_file']);
                $currentTimestamp = time();
                $timeDifference = $currentTimestamp - $creationTimestamp;

                if ($timeDifference > (15 * 60)) {  // 15分钟(秒数)
                    $filteredSessions[] = $session;
                }
            }

            $i = 0;

            if (!empty($filteredSessions)) {
                // 步骤4:更新用户表中的在线状态
                $userIdsToUpdate = array_column($filteredSessions, 'user_id');

                if (Schema::hasTable('users')) {
                    foreach ($userIdsToUpdate as $userId) {
                        $userOnline = User::find($userId);
                        if ($userOnline) {
                            $userOnline->status_online = 0;
                            $userOnline->save();
                            $i++;
                        }
                    }
                } else {
                    echo "用户表不存在。\n";
                }

                // 步骤5:删除筛选出的会话文件
                foreach ($filteredSessions as $session) {
                    $filePath = $sessionPath . '/' . $session['file_name'];
                    if (file_exists($filePath)) {
                        unlink($filePath);
                    }
                }
            }

            echo "处理的过期会话数量:$i\n";

        } else {
            echo "会话目录不存在。\n";
        }

        return 0;
    }
}

该脚本会检查超过15分钟未更新的已登录用户会话,将对应用户的在线状态更新为0并删除会话文件。

随后在/app/Console/Kernel.php中配置该命令每17分钟执行一次:

/**
 * 定义应用的命令调度
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    ...

    $schedule->command('UserSessionCleanup')->cron('*/17 * * * *');
    // ->appendOutputTo('/app/storage/logs/scheduler.log');
     
    ...   
}

场景3:用户活跃交互

当用户登录后持续与系统交互(点击、移动鼠标等),不会触发断开操作。

选择15分钟超时是为了符合PCI DSS 12.3.8的要求。

该方案虽非完美,但可满足需求。


内容的提问来源于stack exchange,提问作者Luizzz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:39:52