编程

Laravel Volt 实时状态

1218 2023-12-16 17:42:00

我们来创建一个 Volt 组件,用它来显示实时用户数量。

概念

要实现该特性,我们需要一个方法来跟踪活跃用户。一个方法是,将活跃用户以日志记入数据库表格。每个条目包含用户 ID 以及何时激活的时间戳。然后,您可以检查最后 5 分钟内或任何其他所需时间段内的活动。另一种方法是在 users 表中添加一个 “last_login_at” 字段。第三种可能性是使用 websockets 服务器来获得活动用户的实时计数。

本文中,我将使用 Redis。Redis 是一个简单的内存数据库,功能强大。我以前用过此方法,它能够扩展到处理数千甚至数百万用户。

首先,我们将创建一个中间件。对于每个 web 请求,该中间件都会为当前用户设置或更新一个 Redis 密钥,该密钥将在 5 分钟后过期。您可以根据需要调整此持续时间。

接下来,我们将使用 Livewire volt 组件,该组件每 5 秒刷新一次,并统计 Redis 中的密钥数量。

按照这些步骤,我们可以很容易地跟踪我们网站上的活跃用户数量。

准备 Volt

首先,需要安装 Livewire 3 和 Volt。

composer require livewire/livewire "^3.0" # Make sure you have Livewire v3.x installed...
composer require livewire/volt "^1.0"

完成以上步骤后,运行如下命令,安装好 Volt:

php artisan volt:install

完成后,我们将着手构建我们的功能。

注意:我假设您已经有一个 Laravel 应用,数据库中有一个 users 表,并且已经配置了 Redis。

使用中间件跟踪用户状态

为了跟踪用户活动,我们将创建一个中间件,每当 Redis 加载网页时,该中间件会将每个唯一用户的活动记录到 Redis。

首先,生成中间件:

php artisan make:middleware LogUserActivity

然后,我们编辑 LogUserActivity 中间件:

 namespace App\Http\Middleware;
 
 use Closure;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Redis;
 use Symfony\Component\HttpFoundation\Response;
 
 class LogUserActivity
 {
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->user()) {
            $userId = $request->user()->id;
            $duration = 300; // This represents 5 minutes

            Redis::setex("live_users:$userId", $duration, 1);
        }

        return $next($request);
    }
}

在这个中间件中,我们检查是否有授权用户。如果是这样,我们在 Redis 中使用 “live_users” 前缀存储一个 key, 其后是用户 ID。

最后,记住将该中间件添加到 Kernel.php 文件,以将其激活。

 namespace App\Http;
 
 class Kernel extends HttpKernel
 {
 	protected $middlewareGroups = [
     'web' => [
         \App\Http\Middleware\EncryptCookies::class,
         \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
         \Illuminate\Session\Middleware\StartSession::class,
         \Illuminate\View\Middleware\ShareErrorsFromSession::class,
         \App\Http\Middleware\VerifyCsrfToken::class,
         \Illuminate\Routing\Middleware\SubstituteBindings::class,
         LogUserActivity::class,
     ],

    'api' => [
         // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
         \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
         \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
  ];
}

为实时活跃用户创建 Volt 组件

我们来创建 Volt 组件,显示实时活跃用户数量。

php artisan make:volt liveusers

该命令在 resources/views/livewire/ 内生成一个名为 liveusers.blade.php 的文件。

为了阐明这些步骤,我将把代码解释分解为两部分:PHP 代码和 HTML 内容。

Volt 内的 PHP 代码

让我们从 Volt 组件的初始化开始:

 use function Livewire\Volt\{computed};
 use Illuminate\Support\Facades\Redis;
 
 $liveUsers = computed(function () {
     $count = 0;
     $cursor = null;
     $pattern = 'live_users:*';
     $batchSize = 1000;
 
     do {
         list($cursor, $keys) = Redis::scan($cursor, $pattern, $batchSize);
         $count += count($keys ?? []);
     } while ($cursor != 0);

     return $count;
});

首先,我们引入必要的函数。computed 函数与 Redis Facade一起从 Livewire\Volt 命名空间引入。

然后,我们使用 computed 函数来获取活动用户数。选择这种方法而不是通常的 state 方法,因为它允许在不重新加载整页的情况下通过长轮询来更新值。

在 computed 回调中,我们使用 scan 方法,这是一种更有效的方法来检索所有前缀为 “live_users:” 的键。在处理大型数据集时,这种方法比 keys 方法更受欢迎,因为它允许我们在不过载内存的情况下迭代大量的项。

HTML 内容

以下是该组件的 HTML 内容:

 <div>
     <div wire:poll.5s>
         <h3 class="text-base font-semibold leading-6 text-gray-900">Live stats</h3>
 
         <dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
             <div class="relative overflow-hidden rounded-lg bg-white px-4 pb-12 pt-5 shadow sm:px-6 sm:pt-6">
                 <dt>
                     <div class="absolute rounded-md bg-indigo-500 p-3">
                         <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                              stroke="currentColor" aria-hidden="true">
                             <path stroke-linecap="round" stroke-linejoin="round"
                                   d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
                         </svg>
                     </div>
                     <p class="ml-16 truncate text-sm font-medium text-gray-500">Live Now</p>
                 </dt>
                 <dd class="ml-16 flex items-baseline">
                     <p class="text-2xl font-semibold text-gray-900">{{ number_format($this->liveUsers, 0) }}</p>
                 </dd>
            </div>
         </dl>
     </div>
</div>

请注意第 2 行中有 wire:poll.5s,用来每 5 秒轮询数据刷新组件。第 10 行获得我们定义的 computed 值并对其格式化。

number_format($this->liveUsers, 0)

因此整个组件长这样

 <?php
 
 use function Livewire\Volt\{state, computed};
 use Illuminate\Support\Facades\Redis;
 
 $liveUsers = computed(function () {
     $count = 0;
     $cursor = null;
     $pattern = 'live_users:*';

     do {
         list($cursor, $keys) = Redis::scan($cursor, $pattern, 1000);
         $count += count($keys ?? []);
     } while ($cursor != 0);
     return $count;
});


?>
<div>
    <div wire:poll.5s>
        <h3 class="text-base font-semibold leading-6 text-gray-900">Live stats</h3>

       <dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
            <div class="relative overflow-hidden rounded-lg bg-white px-4 pb-12 pt-5 shadow sm:px-6 sm:pt-6">
                <dt>
                    <div class="absolute rounded-md bg-indigo-500 p-3">
                        <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                             stroke="currentColor" aria-hidden="true">
                            <path stroke-linecap="round" stroke-linejoin="round"
                                  d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
                        </svg>
                    </div>
                    <p class="ml-16 truncate text-sm font-medium text-gray-500">Live Now</p>
                </dt>
                <dd class="ml-16 flex items-baseline">
                    <p class="text-2xl font-semibold text-gray-900">{{ number_format($this->liveUsers, 0) }}</p>
                </dd>
            </div>
        </dl>
    </div>
</div>

这就是我们要的 volt 组件

Demo 

为了进行实际演示,我设置了一个控制台命令,用于模拟后台的用户活动。这将随机模拟活动用户,让您在网站上实时查看实时计数更新。

collect(Redis::keys('live_users:*'))->each(function ($key) {
    Redis::del($key);
});

foreach(range(1, rand(10, 50)) as $userId) {
    Redis::setex("live_users:$userId", 500, 1);
}

 

  •