Laravel 自增瀏覽數實現(資料庫 + Redis)

zhouzishu發表於2019-08-14

自增瀏覽數

最近一個專案用到自增瀏覽數,首先考慮到的就是在資料表中加個 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 協議》,轉載必須註明作者和本文連結

相關文章