IncreaseViewsListener.php
6.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<?php
/**
+-----------------------------------------------------------------------------------------------------------------------
* 事件层监听:浏览量自增事件类
+-----------------------------------------------------------------------------------------------------------------------
*
* PHP version 7
*
* @category App\Listeners
* @package App\Listeners
* @author Richer <yangzi1028@163.com>
* @date 2020年12月28日14:01:24
* @copyright 2021-2022 Richer (http://www.Richer.com/)
* @license http://www.Richer.com/ License
* @link http://www.Richer.com/
*/
namespace App\Listeners;
use App\Events\IncreaseViews;
use App\Models\Traits\DeviceDetectorTrait;
use DeviceDetector\Parser\Client\Browser;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Schema;
class IncreaseViewsListener
{
use DeviceDetectorTrait;
/**
* 同一post最大访问次数,再刷新数据库
*/
const VIEW_LIMIT = 10; // redis 中访问了多少次后更新数据库
/**
* 同一用户浏览同一post过期时间
*/
const EXPIRE_SECOND = 10; // 用户该时间段内多次刷新只算一次
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param IncreaseViews $event
* @return void
*/
public function handle(IncreaseViews $event)
{
$model = $event->model;
$ip = request()->ip();
$table = $model::TABLE;
$id = $model->id;
//首先判断下 EXPIRE_SECOND 秒时间内,同一IP访问多次,仅仅作为1次访问量
if ($this->ipViewLimit($table, $id, $ip)) {
//一个IP在 EXPIRE_SECOND 秒时间内访问第一次时,刷新下该对象的浏览量
$this->updateCacheViewCount($model, $table, $id, $ip);
}
}
/**
* 一段时间内,限制同一IP访问,防止增加无效浏览次数
* @param $table
* @param $id
* @param $ip
* @return bool
*/
public function ipViewLimit($table, $id, $ip): bool
{
// $ip = '1.1.1.6';
//redis中键值分割都以:来做,可以理解为PHP的命名空间namespace一样
$ipViewKey = $table.':ip:limit:'.$id;
//Redis命令SISMEMBER检查集合类型Set中有没有该键,该指令时间复杂度O(1),Set集合类型中值都是唯一
$existsInRedisSet = Redis::command('SISMEMBER', [$ipViewKey, $ip]);
if (!$existsInRedisSet) {
//SADD,集合类型指令,向ipPostViewKey键中加一个值ip
Redis::command('SADD', [$ipViewKey, $ip]);
//并给该键设置生命时间,这里设置300秒,300秒后同一IP访问就当做是新的浏览量了
Redis::command('EXPIRE', [$ipViewKey, self::EXPIRE_SECOND]);
return true;
}
return false;
}
/**
* 不同用户访问,更新缓存中浏览次数
*
* @param $model
* @param $table
* @param $id
* @param $ip
*/
public function updateCacheViewCount($model, $table, $id, $ip)
{
$cacheKey = $table.':view:'.$id;
// dump($cacheKey);
// 这里以Redis哈希类型存储键,就和数组类似,$cacheKey就类似数组名,$ip为$key.HEXISTS指令判断$key是否存在$cacheKey中
if (Redis::command('HEXISTS', [$cacheKey, $ip])) {
//哈希类型指令HINCRBY,就是给$cacheKey[$ip]加上一个值,这里一次访问就是1
$incre_count = Redis::command('HINCRBY', [$cacheKey, $ip, 1]);
// dump("incre_count:$incre_count");
//redis中这个存储浏览量的值达到30后,就往MySQL里刷下,这样就不需要每一次浏览,来一次query,效率不高
if ($incre_count == self::VIEW_LIMIT) {
$this->updateModelViewCount($model, $table, $id, $incre_count);
// 本篇post,redis中浏览量刷进MySQL后,把该对象的浏览量键抹掉,等着下一次请求重新开始计数
Redis::command('HDEL', [$cacheKey, $ip]);
//同时,抹掉post内容的缓存键,这样就不用等10分钟后再更新view_count了,
//如该篇post在100秒内就达到了30访问量,就在3分钟时更新下MySQL,并把缓存抹掉,下一次请求就从MySQL中请求到最新的view_count,
//当然,100秒内view_count还是缓存的旧数据,极端情况300秒内都是旧数据,而缓存里已经有了29个新增访问量
//实际上也可以这样做:在缓存post的时候,可以把view_count单独拿出来存入键值里如single_view_count,每一次都是给这个值加1,然后把这个值传入视图里
//或者平衡设置下VIEW_LIMIT和EXPIRE_SECOND这两个参数,对于view_count这种实时性要求不高的可以这样做来着
//加上laravel前缀,因为Cache::remember会自动在每一个key前加上laravel前缀,可以看cache.php中这个字段:'prefix' => 'laravel'
Redis::command('DEL', ['laravel:'.$table.':cache:'.$id]);
}
} else {
//哈希类型指令HSET,和数组类似,就像$cacheKey[$ip] = 1;
Redis::command('HSET', [$cacheKey, $ip, '1']);
}
}
/**
* 更新DB中post浏览次数
* @param $model
* @param $count
* @return bool
*/
public function updateModelViewCount($model, $table, $id, $count)
{
// dump($count);
// dump(Schema::hasColumn($table, 'views'));
// 判断是否有该字段 ,如果有该字段就在访问的时候进行增加一次点击量
if (Schema::hasColumn($table, 'views')) {
// $model->where('id', $id)->increment("views", 1);// 自增1
$model->views += $count;
$model->save();
// 判断是否存在方法,不存在退出
// if (method_exists($model, 'accessRecords')) {
// $model->accessRecords()->create([
// 'client_type' => $this->getDeviceType(),
// 'ip' => request()->ip(),
// 'method' => request()->method(),
// 'device_family' => Browser::deviceFamily(),
// 'device_model' => Browser::deviceModel(),
// 'mobile_grade' => Browser::mobileGrade(),
// 'platform_name' => Browser::platformName(),
// 'platform_family' => Browser::platformFamily(),
// 'platform_version' => Browser::platformVersion(),
// 'user_agent' => Browser::userAgent(),
// 'input ' => json_encode(request()->all()),
// 'url' => request()->fullUrl(),
// ]);
//
// return true;
// }
}
}
}