如何统计Laravel项目当前运行的会话数量?
嘿,这个需求我之前帮不少开发者解决过!Laravel里要统计所有活跃会话——不管是登录用户还是逛公开页面的访客,核心得看你用的会话驱动是什么,不同驱动的实现方式差别不小,我给你一步步拆解:
先去 .env 文件里看 SESSION_DRIVER 的值,或者直接查 config/session.php 里的配置。Laravel 默认是 file 驱动,但生产环境很多人会用 database 或 redis——这三种是最常见的,我分别给你讲怎么统计:
1. Database驱动(最省心的方案)
如果用的是 database 驱动,首先得确保你已经生成了会话表:没生成的话跑 php artisan session:table 然后执行迁移。默认会话表叫 sessions,里面的字段刚好能满足统计需求:user_id(登录用户ID,未登录是null)、last_activity(最后活跃时间戳)、ip_address 这些。
统计逻辑
我们通常把“当前活跃”定义为最近N分钟内有过交互(N就是你在 config/session.php 里配置的 lifetime,默认120分钟)。代码示例:
use Illuminate\Support\Facades\DB; // 计算会话过期时间点 $expiration = now()->subMinutes(config('session.lifetime')); // 总活跃会话数 $totalSessions = DB::table('sessions') ->where('last_activity', '>=', $expiration->timestamp) ->count(); // 登录用户数(同一个用户可能开多个会话,要去重) $loggedInUsers = DB::table('sessions') ->where('last_activity', '>=', $expiration->timestamp) ->whereNotNull('user_id') ->distinct('user_id') ->count(); // 访客数 = 总会话数 - 登录用户数 $guests = $totalSessions - $loggedInUsers;
查看详细会话信息
如果要展示每个会话的具体数据(比如用户名称、IP、设备),可以关联用户表查询:
$activeSessions = DB::table('sessions') ->where('last_activity', '>=', $expiration->timestamp) ->leftJoin('users', 'sessions.user_id', '=', 'users.id') ->select( 'sessions.id as session_id', 'users.name as user_name', 'users.email as user_email', 'sessions.ip_address', 'sessions.user_agent', DB::raw('FROM_UNIXTIME(sessions.last_activity) as last_active') ) ->get();
2. File驱动(需要遍历本地文件)
file驱动是Laravel默认的,但统计起来最麻烦——会话都存在 storage/framework/sessions 目录下,每个文件是序列化的会话数据,文件名就是会话ID。
统计逻辑
需要遍历所有会话文件,检查文件修改时间(或者解析内容里的last_activity),过滤掉过期的:
use Illuminate\Support\Facades\Storage; use Illuminate\Session\Store; use Illuminate\Support\Facades\Auth; $sessionDir = storage_path('framework/sessions'); $expiration = now()->subMinutes(config('session.lifetime'))->timestamp; $totalSessions = 0; $loggedInUsers = 0; $loggedInUserIds = []; // 遍历所有会话文件 foreach (Storage::files($sessionDir) as $file) { $fileModifiedTime = Storage::lastModified($file); // 只统计未过期的会话 if ($fileModifiedTime >= $expiration) { $totalSessions++; // 解析会话内容 $sessionContent = Storage::get($file); // 如果会话加密了,必须用SessionStore解密 if (config('session.encrypt')) { $sessionStore = app(Store::class); $sessionData = $sessionStore->getPayloadFromRaw($sessionContent); } else { $sessionData = unserialize($sessionContent); } // 获取登录用户ID(Laravel默认的认证会话键可以用Auth::getRecallerName()获取) $userId = $sessionData[Auth::getRecallerName()] ?? null; if ($userId && !in_array($userId, $loggedInUserIds)) { $loggedInUserIds[] = $userId; $loggedInUsers++; } } } $guests = $totalSessions - $loggedInUsers;
3. Redis驱动(利用Redis键匹配)
如果用Redis存储会话,键名一般是 laravel:session:xxxxxx(前缀看 config/session.php 里的 prefix 配置),我们可以通过匹配键名来统计:
统计逻辑
use Illuminate\Support\Facades\Redis; use Illuminate\Session\Store; use Illuminate\Support\Facades\Auth; $sessionPrefix = config('session.prefix', 'laravel:session'); $expirationMinutes = config('session.lifetime'); // 获取所有会话键 $sessionKeys = Redis::keys("{$sessionPrefix}:*"); $totalSessions = 0; $loggedInUsers = 0; $loggedInUserIds = []; foreach ($sessionKeys as $key) { // 检查键的剩余过期时间,大于0说明未过期 $ttl = Redis::ttl($key); if ($ttl > 0) { $totalSessions++; // 解析会话数据 $sessionData = Redis::get($key); if (config('session.encrypt')) { $sessionStore = app(Store::class); $sessionData = $sessionStore->getPayloadFromRaw($sessionData); } else { $sessionData = unserialize($sessionData); } // 统计登录用户 $userId = $sessionData[Auth::getRecallerName()] ?? null; if ($userId && !in_array($userId, $loggedInUserIds)) { $loggedInUserIds[] = $userId; $loggedInUsers++; } } } $guests = $totalSessions - $loggedInUsers;
- 缓存统计结果:遍历文件/Redis、查询数据库都挺耗时的,建议用
Cache::remember把统计结果缓存5-10分钟,避免每次请求都重复计算。 - 处理加密会话:如果开启了
config('session.encrypt') = true,一定要用Store类的getPayloadFromRaw方法解密,直接反序列化会报错。 - 权限控制:统计会话的页面要加权限验证,比如只允许管理员访问,避免敏感数据泄露。
可以写个简单的控制器方法,把统计结果展示出来:
// routes/web.php 加路由(记得加auth中间件) Route::get('/admin/sessions', [SessionController::class, 'index'])->middleware('auth'); // 控制器方法 public function index() { $expiration = now()->subMinutes(config('session.lifetime')); $totalSessions = 0; $loggedInUsers = 0; $activeSessions = collect(); switch(config('session.driver')) { case 'database': $totalSessions = DB::table('sessions')->where('last_activity', '>=', $expiration->timestamp)->count(); $loggedInUsers = DB::table('sessions')->where('last_activity', '>=', $expiration->timestamp)->whereNotNull('user_id')->distinct('user_id')->count(); $activeSessions = DB::table('sessions') ->where('last_activity', '>=', $expiration->timestamp) ->leftJoin('users', 'sessions.user_id', '=', 'users.id') ->select('sessions.id', 'users.name', 'users.email', 'sessions.ip_address', 'sessions.user_agent', DB::raw('FROM_UNIXTIME(sessions.last_activity) as last_active')) ->get(); break; // 其他驱动的逻辑,比如file/redis // case 'file': ... // case 'redis': ... } $guests = $totalSessions - $loggedInUsers; return view('admin.sessions', compact('totalSessions', 'loggedInUsers', 'guests', 'activeSessions')); }
然后在视图 resources/views/admin/sessions.blade.php 里展示:
<div class="grid grid-cols-3 gap-4 mb-6"> <div class="p-4 bg-blue-50 rounded-lg"> <h3 class="text-sm font-medium text-gray-600">总活跃会话</h3> <p class="text-2xl font-bold text-blue-600">{{ $totalSessions }}</p> </div> <div class="p-4 bg-green-50 rounded-lg"> <h3 class="text-sm font-medium text-gray-600">登录用户</h3> <p class="text-2xl font-bold text-green-600">{{ $loggedInUsers }}</p> </div> <div class="p-4 bg-yellow-50 rounded-lg"> <h3 class="text-sm font-medium text-gray-600">访客</h3> <p class="text-2xl font-bold text-yellow-600">{{ $guests }}</p> </div> </div> <table class="w-full border-collapse"> <thead> <tr class="bg-gray-100"> <th class="px-4 py-2 text-left">会话ID</th> <th class="px-4 py-2 text-left">用户</th> <th class="px-4 py-2 text-left">IP地址</th> <th class="px-4 py-2 text-left">设备</th> <th class="px-4 py-2 text-left">最后活跃</th> </tr> </thead> <tbody> @foreach($activeSessions as $session) <tr class="border-t"> <td class="px-4 py-2">{{ substr($session->id, 0, 10) }}...</td> <td class="px-4 py-2">{{ $session->name ?? '访客' }}</td> <td class="px-4 py-2">{{ $session->ip_address }}</td> <td class="px-4 py-2 text-sm">{{ substr($session->user_agent, 0, 60) }}...</td> <td class="px-4 py-2 text-sm">{{ $session->last_active }}</td> </tr> @endforeach </tbody> </table>
内容的提问来源于stack exchange,提问作者Optimus Prime

