Laravel利用redis和定时任务实现活跃用户统计

2022-09-08 19:14:54 浏览数 (1)

最近在Summer的《Laravel教程-Web开发实战进阶》学到很多东西,以前只会看文档,大概了解Laravel的内容而在实际运用中确不知道该怎样做,碰到一个需求不会立马联想到“这个东西可以用...实现“。 不是有句话”实践是检验真理的唯一标准“ 所以学习编程还是要多实践多写项目,不然就像我一样拿到东西不知道怎样运用 废话不多,今天分享一下利用redis和定时任务实现活跃用户统计 如果你对redis和定时任务不了解建议去补一下linuxredis 以及laravel的任务调度

活跃用户算法

我们规定系统每个小时,统计最近7天用户所发表的帖子数和评论数。 用户发布帖子 4分 用户发布评论 1分 最后计算所有人的得分进行倒序排序 取前八个用户显示在主页活跃用户栏 类似

需求已经明确我们开始编写代码,不过在编写代码之前我们需要.env中指定缓存驱动为redis

为了不让User模型显得非常庞大,我们使用trait的方式编写用户统计逻辑,如果你不了解trait请参照PHP面向对象之trait

新键如下文件 app/Models/Traits/ActiveUserHelper.php 键入如下代码

代码语言:javascript复制
<?php

namespace AppModelsTraits;

use AppModelsTopic;
use AppModelsReply;
use CarbonCarbon;
use Cache;
use DB;
use Arr;

trait ActiveUserHelper
{
    // 用于存放临时用户数据
    protected $users = [];       

    // 配置信息
    protected $topic_weight = 4; // 话题权重
    protected $reply_weight = 1; // 回复权重
    protected $pass_days = 7;    // 多少天内发表过内容
    protected $user_number = 6; // 取出来多少用户

    // 缓存相关配置
    protected $cache_key = 'larabbs_active_users';//设置缓存key
    protected $cache_expire_in_seconds = 65 * 60;//设置缓存过期时间

    public function getActiveUsers()
    {
        // 尝试从缓存中取出 cache_key 对应的数据。如果能取到,便直接返回数据。
        // 否则运行匿名函数中的代码来取出活跃用户数据,返回的同时做了缓存。
        return Cache::remember($this->cache_key, $this->cache_expire_in_seconds, function(){
            return $this->calculateActiveUsers();
        });
    }

    public function calculateAndCacheActiveUsers()
    {
        // 取得活跃用户列表
        $active_users = $this->calculateActiveUsers();
        // 并加以缓存
        $this->cacheActiveUsers($active_users);
    }

    private function calculateActiveUsers()
    {
        $this->calculateTopicScore();//计算获取用户发帖权重
        $this->calculateReplyScore();//计算用户回复权重

        // 数组按照得分排序
        $users = Arr::sort($this->users, function ($user) {
            return $user['score'];
        });

        // 我们需要的是倒序,高分靠前,第二个参数为保持数组的 KEY 不变
        $users = array_reverse($users, true);

        // 只获取我们想要的数量
        $users = array_slice($users, 0, $this->user_number, true);

        // 新建一个空集合
        $active_users = collect();

        foreach ($users as $user_id => $user) {
            // 找寻下是否可以找到用户
            $user = $this->find($user_id);

            // 如果数据库里有该用户的话
            if ($user) {

                // 将此用户实体放入集合的末尾
                $active_users->push($user);
            }
        }

        // 返回数据
        return $active_users;
    }

    private function calculateTopicScore()
    {
        // 从话题数据表里取出限定时间范围($pass_days)内,有发表过话题的用户
        // 并且同时取出用户此段时间内发布话题的数量
        $topic_users = Topic::query()->select(DB::raw('user_id, count(*) as topic_count'))
                                     ->where('created_at', '>=', Carbon::now()->subDays($this->pass_days))
                                     ->groupBy('user_id')
                                     ->get();
        // 根据话题数量计算得分
        foreach ($topic_users as $value) {
            $this->users[$value->user_id]['score'] = $value->topic_count * $this->topic_weight;
        }
    }

    private function calculateReplyScore()
    {
        // 从回复数据表里取出限定时间范围($pass_days)内,有发表过回复的用户
        // 并且同时取出用户此段时间内发布回复的数量
        $reply_users = Reply::query()->select(DB::raw('user_id, count(*) as reply_count'))
                                     ->where('created_at', '>=', Carbon::now()->subDays($this->pass_days))
                                     ->groupBy('user_id')
                                     ->get();
        // 根据回复数量计算得分
        foreach ($reply_users as $value) {
            $reply_score = $value->reply_count * $this->reply_weight;
            if (isset($this->users[$value->user_id])) {
                $this->users[$value->user_id]['score']  = $reply_score;
            } else {
                $this->users[$value->user_id]['score'] = $reply_score;
            }
        }
    }

    private function cacheActiveUsers($active_users)
    {
        // 将数据放入缓存中
        Cache::put($this->cache_key, $active_users, $this->cache_expire_in_seconds);
    }
}

请参照注释理解代码。 由于是基于User Model实现用户活跃统计 我们在User Model trait ActiveUserHelper

这样就相当于把 ActiveUserHelper的代码注入到User Model中

我们要通过定时任务来实现用户活跃统计,所以我们通过命令执行ActiveUserHelper中的逻辑。 新键命令

代码语言:javascript复制
php artisan make:command CalculateActiveUser --command=larabbs:calculate-active-user

CalculateActiveUse是一个命令类,存放在app/Console/Commands/在这里面键入命令相关逻辑

--command=为我们今后要调用的命令名 如 php aritsan larabbs:calculate-active-user

编写app/Console/Commands/CalculateActiveUser

代码语言:javascript复制
<?php

namespace AppConsoleCommands;

use IlluminateConsoleCommand;
use AppModelsUser;

class CalculateActiveUser extends Command
{
    // 供我们调用命令
    protected $signature = 'larabbs:calculate-active-user';

    // 命令的描述
    protected $description = '生成活跃用户';

    // 最终执行的方法
    public function handle(User $user)
    {
        // 在命令行打印一行信息
        $this->info("开始计算...");

        $user->calculateAndCacheActiveUsers();//取得活跃用户并缓存

        $this->info("成功生成!");
    }
}

接下来我们就可以输入 php artisan larabbs:calculate-active-user 执行 上面handle方法

定时任务

我们不可能每一个小时都手动的调用 php artisan larabbs:calculate-active-user 获取最新的活跃用户,所以我们利用定时任务实现 在当前下面输入

代码语言:javascript复制
export EDITOR=vi && crontab -e

打开如下,在文件的末尾键入

代码语言:javascript复制
* * * * * php /home/vagrant/Code/larabbs/artisan schedule:run >> /dev/null 2>&1

* * * * * php /home/vagrant/Code/larabbs/art。。。。。是linux定时任务的写法 参照下图

图为我们要执行的 >>表示附加 linux 中dev/null表示程序黑洞的英文。 >表示覆盖内容。 1 stdout 标准输出。 2 表示 stderr 标准错误。 所以表示 2>&1 将所有错误信息覆盖到标准输出。 综上所述 ` php /home/vagrant/Code/larabbs/artisan schedule:run >> /dev/null 2>&1 `表示将 schedule:run 的结果与错误输出到黑洞(不予预显示),不过为了方便调试,你可以将其运行输出到文件中

* * * * *php artisan schedule:run >> /home/user/output.txt

我们注册调度任务 在app/Console/Kernel.php

代码语言:javascript复制
<?php

namespace AppConsole;

use IlluminateConsoleSchedulingSchedule;
use IlluminateFoundationConsoleKernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        //
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  IlluminateConsoleSchedulingSchedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // $schedule->command('inspire')
        //          ->hourly();

        // 一小时执行一次『活跃用户』数据生成的命令
        $schedule->command('larabbs:calculate-active-user')->hourly();
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}

这样每一个小时就会执行schedule里面的命令,参照laravel任务调度了解更多。

0 人点赞