作者

一、需求介绍

一篇文章或者帖子的浏览次数的统计,如果只是每次增加一个浏览量 就到数据库新增/修改一个数据,请求频繁 用户量一多就出问题了。

二、解决方案

1、每次增加一个访问量就在缓存中去进行更改

2、达到一定数量后刷新改变Mysql数据库,这样数据也是准确的 效率也比直接每次刷新数据库要高出许多

本次教程是以测试项目为案例来实现此功能,如需在正式项目中开发,请自行集成!

三、功能实践

1、创建新项目:laravel new test,在 web.php 中添加如下路由:

Route::get('/post/{id}', 'PostController@showPost');

2、创建控制器和对应方法:

php artisan make:controller PostController

3、创建数据库test和数据表posts,并修改 .env文件:

随便插点数据进去。

4、加入事件监听:

app/providers/EventServiceProvider 中的$listen加入

protected $listen = [
        'App\Events\PostViewEvent' => [
            'App\Listeners\PostEventListener',
        ],
    ];

5、生成事件监听:

php artisan event:generate

6、在PostViewEvent.php中写入如下代码:

<?php

namespace App\Events;
use App\Post;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class PostViewEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $ip;
    public $post;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Post $post, $ip)
    {
        $this->post = $post;
        $this->ip = $ip;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

7、在 app\Listeners\PostEventListener中加入如下代码:

<?php

namespace App\Listeners;

use App\Events\PostViewEvent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

use App\Post;
use Illuminate\Support\Facades\Redis;

class PostEventListener
{
    /**
     * 一个帖子的最大访问数
     */
    const postViewLimit = 2;
    /**
     * 同一用户浏览同一个帖子的过期时间
     */
    const ipExpireSec = 200;

    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  PostViewEvent  $event
     * @return void
     */
    public function handle(PostViewEvent $event)
    {
        $post = $event->post;
        $ip = $event->ip;
//        $ip = "127.2.1.31";
        $id = $post->id;
        //首先判断下ipExpireSec = 200秒时间内,同一IP访问多次,仅仅作为1次访问量
        if($this->ipViewLimit($id, $ip)){
            //一个IP在300秒时间内访问第一次时,刷新下该篇post的浏览量
            $this->updateCacheViewCount($id, $ip);
        }

    }

    /**
     * 限制同一IP一段时间内得访问,防止增加无效浏览次数
     * @param $id
     * @param $ip
     * @return bool
     */
    public function ipViewLimit($id, $ip)
    {
        $ipPostViewKey = 'post:ip:limit:'.$id;
        //Redis命令SISMEMBER检查集合类型Set中有没有该键,Set集合类型中值都是唯一
        $existsInRedisSet = Redis::command('SISMEMBER', [$ipPostViewKey, $ip]);
        //如果集合中不存在这个建 那么新建一个并设置过期时间
        if(!$existsInRedisSet){
            //SADD,集合类型指令,向ipPostViewKey键中加一个值ip
            Redis::command('SADD', [$ipPostViewKey, $ip]);
            //并给该键设置生命时间,这里设置300秒,300秒后同一IP访问就当做是新的浏览量了
            Redis::command('EXPIRE', [$ipPostViewKey, self::ipExpireSec]);
            return true;
        }
        return false;
    }

    /**
     * 达到要求更新数据库的浏览量
     * @param $id
     * @param $count
     */
    public function updateModelViewCount($id, $count)
    {
        //访问量达到300,再进行一次SQL更新
        $post = Post::find($id);
        $post->view_count += $count;
        $post->save();
    }

    /**
     * 不同用户访问,更新缓存中浏览次数
     * @param $id
     * @param $ip
     */
    public function updateCacheViewCount($id, $ip)
    {
        $cacheKey = 'post:view:'.$id;
        //这里以Redis哈希类型存储键,就和数组类似,$cacheKey就类似数组名 如果这个key存在
        if(Redis::command('HEXISTS', [$cacheKey, $ip])){
            //哈希类型指令HINCRBY,就是给$cacheKey[$ip]加上一个值,这里一次访问就是1
            $save_count = Redis::command('HINCRBY', [$cacheKey, $ip, 1]);
            //redis中这个存储浏览量的值达到30后,就去刷新一次数据库
            if($save_count == self::postViewLimit){
                $this->updateModelViewCount($id, $save_count);
                //本篇post,redis中浏览量刷进MySQL后,就把该篇post的浏览量清空,重新开始计数
                Redis::command('HDEL', [$cacheKey, $ip]);
                Redis::command('DEL', ['laravel:post:cache:'.$id]);
            }
        }else{
            //哈希类型指令HSET,和数组类似,就像$cacheKey[$ip] = 1;
            Redis::command('HSET', [$cacheKey, $ip, '1']);
        }
    }

}

8、在PostController控制器中添加如下代码:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use App\Post;
use App\Events\PostViewEvent;
use Illuminate\Support\Facades\Cache;
class PostController extends Controller
{
    private $cacheExpires = 60;//post文章数据缓存时间 s
    public function showPost(Request $request,$id){
        //存入缓存中,该键值key='post:cache'.$id生命时间60分钟
        $post = Cache::remember('post:cache:'.$id, $this->cacheExpires, function () use ($id) {
            return Post::whereId($id)->first();
        });

        //获取客户端请求的IP
        $ip = $request->ip();
//        $ip = "127.0.1.1";//手动更改ip 以不同ip访问,计数
        //触发浏览次数统计时间
        event(new PostViewEvent($post, $ip));
        dump("当前预览文章的名称:".$post->title);
        dump("当前预览数:".$post->view_count);
    }
}

9、重启终端,浏览器访问 http://127.0.0.1:8000,你会看到 redis报错,接下来执行如下代码,安装redis扩展。

注意:执行下面的命令之前,请确保你的redis已经安装好了。

composer require predis/predis

10、启动redis,执行命令:redis-server。 11、再次访问浏览器,你会看到如下结果:

最后:测试,上面的代码设置的是200秒时间内,同一IP访问多次,仅仅作为1次访问量,所以等待200秒后再次刷新,浏览数加1

Well Done!!!

转载请注明,来自https://itfun.tv/news/153