本文目录导读:

- 方案一:基于Session的简单实现
- 方案二:使用Redis/Predis存储在线状态(推荐)
- 方案三:使用Laravel Echo + Socket.IO(实时)
- 方案四:API接口实现(适用于前后端分离)
- 最佳实践建议
- 使用示例
我来为您介绍在Laravel项目中实现在线用户状态的几种方案:
基于Session的简单实现
数据库迁移
// database/migrations/create_sessions_table.php
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
$table->timestamps();
});
创建在线状态中间件
// app/Http/Middleware/UpdateUserActivity.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
class UpdateUserActivity
{
public function handle($request, Closure $next)
{
if (Auth::check()) {
$expiresAt = now()->addMinutes(5); // 5分钟后过期
Cache::put('user-is-online-' . Auth::user()->id, true, $expiresAt);
Auth::user()->update([
'last_seen_at' => now()
]);
}
return $next($request);
}
}
配置中间件
// app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\UpdateUserActivity::class,
],
];
用户模型更新
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
class User extends Model
{
// 判断用户是否在线
public function isOnline()
{
return Cache::has('user-is-online-' . $this->id);
}
// 获取在线用户
public static function onlineUsers()
{
return self::where('last_seen_at', '>=', now()->subMinutes(5))
->get();
}
}
使用Redis/Predis存储在线状态(推荐)
安装Redis
composer require predis/predis
配置Redis
// config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
],
创建在线用户服务
// app/Services/OnlineUserService.php
namespace App\Services;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
class OnlineUserService
{
private const ONLINE_KEY = 'online_users';
private const TIMEOUT = 300; // 5分钟
public function markOnline($userId)
{
Redis::zadd(self::ONLINE_KEY, time(), $userId);
Redis::expire(self::ONLINE_KEY, self::TIMEOUT);
}
public function markOffline($userId)
{
Redis::zrem(self::ONLINE_KEY, $userId);
}
public function getOnlineUsers()
{
$onlineUsers = Redis::zrange(self::ONLINE_KEY, 0, -1);
return array_map('intval', $onlineUsers);
}
public function isUserOnline($userId)
{
$score = Redis::zscore(self::ONLINE_KEY, $userId);
return $score !== false && (time() - $score) < self::TIMEOUT;
}
public function cleanExpired()
{
$expireTime = time() - self::TIMEOUT;
Redis::zremrangebyscore(self::ONLINE_KEY, 0, $expireTime);
}
}
创建事件和监听器
// app/Events/UserOnline.php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserOnline
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId;
public function __construct(int $userId)
{
$this->userId = $userId;
}
}
监听器
// app/Listeners/HandleUserOnline.php
namespace App\Listeners;
use App\Events\UserOnline;
use App\Services\OnlineUserService;
class HandleUserOnline
{
private $onlineService;
public function __construct(OnlineUserService $onlineService)
{
$this->onlineService = $onlineService;
}
public function handle(UserOnline $event)
{
$this->onlineService->markOnline($event->userId);
}
}
在线状态控制器
// app/Http/Controllers/OnlineStatusController.php
namespace App\Http\Controllers;
use App\Services\OnlineUserService;
use Illuminate\Http\Request;
class OnlineStatusController extends Controller
{
private $onlineService;
public function __construct(OnlineUserService $onlineService)
{
$this->onlineService = $onlineService;
}
// 更新在线状态(前端定时请求)
public function updateStatus(Request $request)
{
if ($request->user()) {
$this->onlineService->markOnline($request->user()->id);
return response()->json(['success' => true]);
}
return response()->json(['error' => 'Unauthorized'], 401);
}
// 获取在线用户列表
public function onlineUsers()
{
$onlineUsers = $this->onlineService->getOnlineUsers();
$users = \App\Models\User::whereIn('id', $onlineUsers)
->select('id', 'name', 'avatar')
->get();
return response()->json(['users' => $users]);
}
}
使用Laravel Echo + Socket.IO(实时)
安装依赖
composer require pusher/pusher-php-server npm install laravel-echo pusher-js
配置广播
// config/broadcasting.php
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_CLUSTER'),
'encrypted' => true,
],
],
],
创建用户在线状态事件
// app/Events/UserPresenceChanged.php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
class UserPresenceChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets;
public $userId;
public $isOnline;
public function __construct($userId, $isOnline)
{
$this->userId = $userId;
$this->isOnline = $isOnline;
}
public function broadcastOn()
{
return new Channel('users');
}
public function broadcastAs()
{
return 'user.presence.changed';
}
}
前端实时监听
// resources/js/bootstrap.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true
});
// 监听用户状态变更
Echo.channel('users')
.listen('.user.presence.changed', (e) => {
updateUserStatus(e.userId, e.isOnline);
});
API接口实现(适用于前后端分离)
在线状态表迁移
// database/migrations/create_user_presence_table.php
Schema::create('user_presence', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unique();
$table->timestamp('last_seen_at');
$table->string('ip_address')->nullable();
$table->string('user_agent')->nullable();
$table->boolean('is_online')->default(false);
$table->timestamps();
});
心跳接口
// routes/api.php
Route::middleware('auth:sanctum')->post('/presence/heartbeat', function (Request $request) {
$user = $request->user();
$presence = UserPresence::updateOrCreate(
['user_id' => $user->id],
[
'last_seen_at' => now(),
'is_online' => true,
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent()
]
);
return response()->json(['success' => true]);
});
// 获取在线用户
Route::get('/presence/online-users', function () {
$onlineUsers = UserPresence::where('is_online', true)
->where('last_seen_at', '>=', now()->subMinutes(5))
->with('user:id,name,avatar')
->get();
return response()->json($onlineUsers);
});
前端心跳实现
// 前端心跳更新
function updatePresence() {
axios.post('/api/presence/heartbeat')
.then(response => {
console.log('Presence updated');
})
.catch(error => {
console.error('Error updating presence:', error);
});
}
// 每4分钟更新一次
setInterval(updatePresence, 4 * 60 * 1000);
最佳实践建议
中间件自动更新
// app/Http/Middleware/AutoUpdatePresence.php
namespace App\Http\Middleware;
use Closure;
use App\Services\OnlineUserService;
class AutoUpdatePresence
{
protected $onlineService;
public function __construct(OnlineUserService $onlineService)
{
$this->onlineService = $onlineService;
}
public function handle($request, Closure $next)
{
$response = $next($request);
if (auth()->check()) {
$this->onlineService->markOnline(auth()->id());
}
return $response;
}
}
清理过期数据
// 使用定时任务清理过期数据
// app/Console/Commands/CleanExpiredPresence.php
class CleanExpiredPresence extends Command
{
protected $signature = 'presence:clean';
public function handle(OnlineUserService $service)
{
$service->cleanExpired();
$this->info('Cleaned expired presence data.');
}
}
缓存优化
// 使用缓存提高性能
$onlineStatus = Cache::remember("user-presence-{$userId}", 60, function () use ($userId) {
return $this->onlineService->isUserOnline($userId);
});
异步处理
// 将在线状态更新放到队列中
Illuminate\Support\Facades\Queue::push(function ($job) {
$job->setTimeout(5);
$service = app(OnlineUserService::class);
$service->markOnline($job->userId);
});
使用示例
// 在视图中显示用户在线状态
@if($user->isOnline())
<span class="online-status">在线</span>
@else
<span class="offline-status">离线</span>
@endif
// 获取在线用户数 $onlineCount = $onlineUserService->getOnlineUsersCount();
// 在管理员面板显示在线用户
foreach ($onlineUsers as $user) {
echo "{$user->name} - 最后活跃:{$user->last_seen_at->diffForHumans()}";
}
选择哪种方案取决于您的具体需求:
- 简单场景:使用Session方案
- 多服务器场景:使用Redis方案
- 实时性要求高:使用WebSocket方案
- 前后端分离:使用API方案
记得根据实际情况调整超时时间和心跳频率。