PHP项目Laravel在线用户状态实现

wen PHP项目 3

本文目录导读:

PHP项目Laravel在线用户状态实现

  1. 方案一:基于Session的简单实现
  2. 方案二:使用Redis/Predis存储在线状态(推荐)
  3. 方案三:使用Laravel Echo + Socket.IO(实时)
  4. 方案四:API接口实现(适用于前后端分离)
  5. 最佳实践建议
  6. 使用示例

我来为您介绍在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方案

记得根据实际情况调整超时时间和心跳频率。

抱歉,评论功能暂时关闭!