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