First, demand introduction
Views of an article or post statistics, if only in increments of views on a database to add / modify a data, the amount of more than one request frequent user on the problem.
Second, the solution
1, each time a traffic increase in the cache to make changes
2, after reaching a certain number of refreshing change Mysql database so that the data is accurate and efficient than direct each refresh database to be much higher
The tutorial is a test case for the project to implement this feature. For the development of a formal project, integrated own!
Third, the functional practice
1, create a new project: laravel new test
in web.php
add the following route:
Route::get('/post/{id}', 'PostController@showPost');
2, the controller and the corresponding method create:
php artisan make:controller PostController
3, create a database test
and data tables posts
, and modify the .env
file:
Just plug point data into it.
4, add event listeners:
In app/providers/EventServiceProvider
the $listen
added
protected $listen = [
'App\Events\PostViewEvent' => [
'App\Listeners\PostEventListener',
],
];
5, generates event listeners:
php artisan event:generate
6, the PostViewEvent.php
write code as follows:
<?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, in app\Listeners\PostEventListener
the addition of the following code:
<?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, the PostController
add the following code controller:
<?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, restart the terminal, browser access http://127.0.0.1:8000
, you will see redis
an error, then execute the following code, install redis
extensions.
Note: Before executing the following command, make sure that you
redis
have installed.
composer require predis/predis
10, start redis
, execute the redis-server
command: .
11, access the browser again, you will see the following results:
And finally: test the above code is provided 200
within seconds, the same IP access many times, just as once visited, so wait 200
refresh again after seconds Views plus 1
.
Well Done!!!