Laravel 自增浏览数实现(数据库 + Redis)

自增浏览数

最近一个项目用到自增浏览数,首先考虑到的就是在数据表中加个 view_count 字段,并在每次访问时 increment 自增。日复一日,每次访问执行 update,对性能是十分不利的。

看了 9.4. 用户最后登录时间《L02 Laravel 教程 - Web 开发实战进阶 ( Laravel 5... ,就使用 Redis + 数据库 实现了类似的功能。

数据库字段

首先需要在数据表中加入一个 view_count 字段:

    $table->unsignedInteger('view_count')->default(0);

增加 ViewCountsHelper Trait

废话不多说,直接上代码,都有完整的注释。思路来自 https://github.com/summerblue/larabbs/tree...

<?php

namespace App\Models\Traits;

use Redis;
use Carbon\Carbon;

trait ViewCountsHelper {
    // 缓存相关
    protected $hash_prefix = 'topic_view_counts_';
    protected $field_prefix = 'topic_';

    public function viewCountIncrement()
    {
        // 获取今日 Redis 哈希表名称,如:topic_view_counts_2017-10-21
        $hash = $this->getHashFromDateString(Carbon::now()->toDateString());

        // 字段名称,如:topic_1
        $field = $this->getHashField();

        // 当前阅读数,如果存在就自增,否则就为 1
        $count = Redis::hGet($hash, $field);
        if ($count) {
            $count++;
        } else {
            $count = 1;
        }

        // 数据写入 Redis ,字段已存在会被更新
        Redis::hSet($hash, $field, $count);
    }

    public function syncTopicViewCounts()
    {
        // 获取昨日的哈希表名称,如:topic_view_counts_2017-10-21
        $hash = $this->getHashFromDateString(Carbon::now()->toDateString());

        // 从 Redis 中获取所有哈希表里的数据
        $counts = Redis::hGetAll($hash);

        // 如果没有任何数据直接 return
        if (count($counts) === 0) {
            return;
        }

        // 遍历,并同步到数据库中
        foreach ($counts as $topic_id => $view_count) {
            // 会将 `topic_1` 转换为 1
            $topic_id = str_replace($this->field_prefix, '', $topic_id);

            // 只有当话题存在时才更新到数据库中
            if ($topic = $this->find($topic_id)) {
                $topic->view_count = $this->attribute['view_count'] + $view_count;
                $topic->save();
            }
        }

        // 以数据库为中心的存储,既已同步,即可删除
        Redis::del($hash);
    }

    public function getViewCountAttribute($value)
    {
        // 获取今日对应的哈希表名称
        $hash = $this->getHashFromDateString(Carbon::now()->toDateString());

        // 字段名称,如:topic_1
        $field = $this->getHashField();

        // 三元运算符,优先选择 Redis 的数据,否则使用数据库中
        $count = Redis::hGet($hash, $field) ? : $value;

        // 如果存在的话,返回 数据库中的阅读数 加上 Redis 中的阅读数
        if ($count) {
            return $this->attribute['view_count'] + $count;
        } else {
            // 否则返回 0
            return 0;
        }
    }

    public function getHashFromDateString($date)
    {
        // Redis 哈希表的命名,如:topic_view_counts_2017-10-21
        return $this->hash_prefix . $date;
    }

    public function getHashField()
    {
        // 字段名称,如:topic_1
        return $this->field_prefix . $this->id;
    }
}

然后在需要此功能的模型中 use Traits\ViewCountsHelper 即可。

每天将浏览量同步到数据库

app/Console/Commands/SyncTopicViewCounts.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Topic;

class SyncTopicViewCounts extends Command
{
    protected $signature = 'topic:sync-topic-view-counts';
    protected $description = '将话题 view_count 从 Redis 同步到数据库中';

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle(Topic $topic)
    {
        $topic->syncTopicViewCounts();
        $this->info("同步成功!");
    }
}

app/Console/Kernel.php

    protected function schedule(Schedule $schedule)
    {
        // ...
        $schedule->command('topic:sync-topic-view-counts')->dailyAt('00:00');
    }

使用

<?php

namespace App\Http\Controllers;

use App\Models\Topic;
use Illuminate\Http\Request;

class TopicsController extends Controller
{
    public function show(Topic $topic)
    {
        $topic->viewCountIncrement(); // 自增浏览数

        dd($topic->view_count); // 获取浏览数
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 5
nfangxu

重复造轮子了, 兄嘚

4年前 评论

@nfangxu 这个拓展包好像只能存 redis ,没有同步到数据库 :joy:

4年前 评论

每天零点同步到MySQL,$topic->view_count 同步返回数据还没有更新吧。

public function show(Topic $topic)
    {
        $topic->viewCountIncrement(); // 自增浏览数

        dd($topic->view_count); // 获取浏览数
    }
4年前 评论

测试了下,似乎只能统计当天的访问量,没有将之前的访问量 累计起来

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!