设为首页 收藏本站
查看: 1106|回复: 0

[经验分享] 基于Redis位图实现系统用户登录统计

[复制链接]

尚未签到

发表于 2017-12-21 06:52:01 | 显示全部楼层 |阅读模式
1 <?php  

  2  
  3 namespace Lib\LoginLog;
  
  4 use Lib\CLogFileHandler;
  
  5 use Lib\HObject;
  
  6 use Lib\Log;
  
  7 use Lib\Tools;
  
  8
  
  9 /**
  
10  * 登录日志操作类
  
11  * User: dbn
  
12  * Date: 2017/10/11
  
13  * Time: 12:01
  
14  * ------------------------
  
15  * 日志最小粒度为:天
  
16  */
  
17

  
18>  
19 {
  
20     private $_redisHandle; // Redis登录日志处理
  
21     private $_dbHandle;    // 数据库登录日志处理
  
22
  
23     public function __construct()
  
24     {
  
25         $this->_redisHandle = new LoginLogRedisHandle($this);
  
26         $this->_dbHandle    = new LoginLogDBHandle($this);
  
27
  
28         // 初始化日志
  
29         $logHandler = new CLogFileHandler(__DIR__ . '/Logs/del.log');
  
30         Log::Init($logHandler, 15);
  
31     }
  
32
  
33     /**
  
34      * 记录登录:每天只记录一次登录,只允许设置当月内登录记录
  
35      * @param  string $type 用户类型
  
36      * @param  int    $uid  唯一标识(用户ID)
  
37      * @param  int    $time 时间戳
  
38      * @return boolean
  
39      */
  
40     public function setLogging($type, $uid, $time)
  
41     {
  
42         $key = $this->_redisHandle->getLoginLogKey($type, $uid, $time);
  
43         if ($this->_redisHandle->checkLoginLogKey($key)) {
  
44             return $this->_redisHandle->setLogging($key, $time);
  
45         }
  
46         return false;
  
47     }
  
48
  
49     /**
  
50      * 查询用户某一天是否登录过
  
51      * @param  string $type 用户类型
  
52      * @param  int    $uid  唯一标识(用户ID)
  
53      * @param  int    $time 时间戳
  
54      * @return boolean 参数错误或未登录过返回false,登录过返回true
  
55      */
  
56     public function getDateWhetherLogin($type, $uid, $time)
  
57     {
  
58         $key = $this->_redisHandle->getLoginLogKey($type, $uid, $time);
  
59         if ($this->_redisHandle->checkLoginLogKey($key)) {
  
60
  
61             // 判断Redis中是否存在记录
  
62             $isRedisExists = $this->_redisHandle->checkRedisLogExists($key);
  
63             if ($isRedisExists) {
  
64
  
65                 // 从Redis中进行判断
  
66                 return $this->_redisHandle->dateWhetherLogin($key, $time);
  
67             } else {
  
68
  
69                 // 从数据库中进行判断
  
70                 return $this->_dbHandle->dateWhetherLogin($type, $uid, $time);
  
71             }
  
72         }
  
73         return false;
  
74     }
  
75
  
76     /**
  
77      * 查询用户某月是否登录过
  
78      * @param  string $type 用户类型
  
79      * @param  int    $uid  唯一标识(用户ID)
  
80      * @param  int    $time 时间戳
  
81      * @return boolean 参数错误或未登录过返回false,登录过返回true
  
82      */
  
83     public function getDateMonthWhetherLogin($type, $uid, $time)
  
84     {
  
85         $key = $this->_redisHandle->getLoginLogKey($type, $uid, $time);
  
86         if ($this->_redisHandle->checkLoginLogKey($key)) {
  
87
  
88             // 判断Redis中是否存在记录
  
89             $isRedisExists = $this->_redisHandle->checkRedisLogExists($key);
  
90             if ($isRedisExists) {
  
91
  
92                 // 从Redis中进行判断
  
93                 return $this->_redisHandle->dateMonthWhetherLogin($key);
  
94             } else {
  
95
  
96                 // 从数据库中进行判断
  
97                 return $this->_dbHandle->dateMonthWhetherLogin($type, $uid, $time);
  
98             }
  
99         }
  
100         return false;
  
101     }
  
102
  
103     /**
  
104      * 查询用户在某个时间段是否登录过
  
105      * @param  string $type 用户类型
  
106      * @param  int    $uid  唯一标识(用户ID)
  
107      * @param  int    $startTime 开始时间戳
  
108      * @param  int    $endTime   结束时间戳
  
109      * @return boolean 参数错误或未登录过返回false,登录过返回true
  
110      */
  
111     public function getTimeRangeWhetherLogin($type, $uid, $startTime, $endTime){
  
112         $result = $this->getUserTimeRangeLogin($type, $uid, $startTime, $endTime);
  
113         if ($result['hasLog']['count'] > 0) {
  
114             return true;
  
115         }
  
116         return false;
  
117     }
  
118
  
119     /**
  
120      * 获取用户某时间段内登录信息
  
121      * @param  string $type      用户类型
  
122      * @param  int    $uid       唯一标识(用户ID)
  
123      * @param  int    $startTime 开始时间戳
  
124      * @param  int    $endTime   结束时间戳
  
125      * @return array  参数错误或未查询到返回array()
  
126      * -------------------------------------------------
  
127      * 查询到结果:
  
128      * array(
  
129      *      'hasLog' => array(
  
130      *          'count' => n,                                  // 有效登录次数,每天重复登录算一次
  
131      *          'list' => array('2017-10-1', '2017-10-15' ...) // 有效登录日期
  
132      *      ),
  
133      *      'notLog' => array(
  
134      *          'count' => n,                                  // 未登录次数
  
135      *          'list' => array('2017-10-1', '2017-10-15' ...) // 未登录日期
  
136      *      )
  
137      * )
  
138      */
  
139     public function getUserTimeRangeLogin($type, $uid, $startTime, $endTime)
  
140     {
  
141         $hasCount   = 0;       // 有效登录次数
  
142         $notCount   = 0;       // 未登录次数
  
143         $hasList    = array(); // 有效登录日期
  
144         $notList    = array(); // 未登录日期
  
145         $successFlg = false;   // 查询到数据标识
  
146
  
147         if ($this->checkTimeRange($startTime, $endTime)) {
  
148
  
149             // 获取需要查询的Key
  
150             $keyList = $this->_redisHandle->getTimeRangeRedisKey($type, $uid, $startTime, $endTime);
  
151
  
152             if (!empty($keyList)) {
  
153                 foreach ($keyList as $key => $val) {
  
154
  
155                     // 判断Redis中是否存在记录
  
156                     $isRedisExists = $this->_redisHandle->checkRedisLogExists($val['key']);
  
157                     if ($isRedisExists) {
  
158
  
159                         // 存在,直接从Redis中获取
  
160                         $logInfo = $this->_redisHandle->getUserTimeRangeLogin($val['key'], $startTime, $endTime);
  
161                     } else {
  
162
  
163                         // 不存在,尝试从数据库中读取
  
164                         $logInfo = $this->_dbHandle->getUserTimeRangeLogin($type, $uid, $val['time'], $startTime, $endTime);
  
165                     }
  
166
  
167                     if (is_array($logInfo)) {
  
168                         $hasCount += $logInfo['hasLog']['count'];
  
169                         $hasList = array_merge($hasList, $logInfo['hasLog']['list']);
  
170                         $notCount += $logInfo['notLog']['count'];
  
171                         $notList = array_merge($notList, $logInfo['notLog']['list']);
  
172                         $successFlg = true;
  
173                     }
  
174                 }
  
175             }
  
176         }
  
177
  
178         if ($successFlg) {
  
179             return array(
  
180                 'hasLog' => array(
  
181                     'count' => $hasCount,
  
182                     'list'  => $hasList
  
183                 ),
  
184                 'notLog' => array(
  
185                     'count' => $notCount,
  
186                     'list'  => $notList
  
187                 )
  
188             );
  
189         }
  
190
  
191         return array();
  
192     }
  
193
  
194     /**
  
195      * 获取某段时间内有效登录过的用户 统一接口
  
196      * @param  int    $startTime 开始时间戳
  
197      * @param  int    $endTime   结束时间戳
  
198      * @param  array  $typeArr   用户类型,为空时获取全部类型
  
199      * @return array  参数错误或未查询到返回array()
  
200      * -------------------------------------------------
  
201      * 查询到结果:指定用户类型
  
202      * array(
  
203      *      'type1' => array(
  
204      *          'count' => n,                     // type1 有效登录总用户数
  
205      *          'list' => array('111', '222' ...) // type1 有效登录用户
  
206      *      ),
  
207      *      'type2' => array(
  
208      *          'count' => n,                     // type2 有效登录总用户数
  
209      *          'list' => array('333', '444' ...) // type2 有效登录用户
  
210      *      )
  
211      * )
  
212      * -------------------------------------------------
  
213      * 查询到结果:未指定用户类型,全部用户,固定键 'all'
  
214      * array(
  
215      *      'all' => array(
  
216      *          'count' => n,                     // 有效登录总用户数
  
217      *          'list' => array('111', '222' ...) // 有效登录用户
  
218      *      )
  
219      * )
  
220      */
  
221     public function getOrientedTimeRangeLogin($startTime, $endTime, $typeArr = array())
  
222     {
  
223         if ($this->checkTimeRange($startTime, $endTime)) {
  
224
  
225             // 判断是否指定类型
  
226             if (is_array($typeArr) && !empty($typeArr)) {
  
227
  
228                 // 指定类型,验证类型合法性
  
229                 if ($this->checkTypeArr($typeArr)) {
  
230
  
231                     // 依据类型获取
  
232                     return $this->getSpecifyTypeTimeRangeLogin($startTime, $endTime, $typeArr);
  
233                 }
  
234             } else {
  
235
  
236                 // 未指定类型,统一获取
  
237                 return $this->getSpecifyAllTimeRangeLogin($startTime, $endTime);
  
238             }
  
239         }
  
240         return array();
  
241     }
  
242
  
243     /**
  
244      * 指定类型:获取某段时间内登录过的用户
  
245      * @param  int    $startTime 开始时间戳
  
246      * @param  int    $endTime   结束时间戳
  
247      * @param  array  $typeArr   用户类型
  
248      * @return array
  
249      */
  
250     private function getSpecifyTypeTimeRangeLogin($startTime, $endTime, $typeArr)
  
251     {
  
252         $data = array();
  
253         $successFlg = false; // 查询到数据标识
  
254
  
255         // 指定类型,根据类型单独获取,进行整合
  
256         foreach ($typeArr as $typeArrVal) {
  
257
  
258             // 获取需要查询的Key
  
259             $keyList = $this->_redisHandle->getSpecifyTypeTimeRangeRedisKey($typeArrVal, $startTime, $endTime);
  
260             if (!empty($keyList)) {
  
261
  
262                 $data[$typeArrVal]['count'] = 0;       // 该类型下有效登录用户数
  
263                 $data[$typeArrVal]['list']  = array(); // 该类型下有效登录用户
  
264
  
265                 foreach ($keyList as $keyListVal) {
  
266
  
267                     // 查询Kye,验证Redis中是否存在:此处为单个类型,所以直接看Redis中是否存在该类型Key即可判断是否存在
  
268                     // 存在的数据不需要去数据库中去查看
  
269                     $standardKeyList = $this->_redisHandle->getKeys($keyListVal['key']);
  
270                     if (is_array($standardKeyList) && count($standardKeyList) > 0) {
  
271
  
272                         // Redis存在
  
273                         foreach ($standardKeyList as $standardKeyListVal) {
  
274
  
275                             // 验证该用户在此时间段是否登录过
  
276                             $redisCheckLogin = $this->_redisHandle->getUserTimeRangeLogin($standardKeyListVal, $startTime, $endTime);
  
277                             if ($redisCheckLogin['hasLog']['count'] > 0) {
  
278
  
279                                 // 同一个用户只需记录一次
  
280                                 $uid = $this->_redisHandle->getLoginLogKeyInfo($standardKeyListVal, 'uid');
  
281                                 if (!in_array($uid, $data[$typeArrVal]['list'])) {
  
282                                     $data[$typeArrVal]['count']++;
  
283                                     $data[$typeArrVal]['list'][] = $uid;
  
284                                 }
  
285                                 $successFlg = true;
  
286                             }
  
287                         }
  
288
  
289                     } else {
  
290
  
291                         // 不存在,尝试从数据库中获取
  
292                         $dbResult = $this->_dbHandle->getTimeRangeLoginSuccessUser($keyListVal['time'], $startTime, $endTime, $typeArrVal);
  
293                         if (!empty($dbResult)) {
  
294                             foreach ($dbResult as $dbResultVal) {
  
295                                 if (!in_array($dbResultVal, $data[$typeArrVal]['list'])) {
  
296                                     $data[$typeArrVal]['count']++;
  
297                                     $data[$typeArrVal]['list'][] = $dbResultVal;
  
298                                 }
  
299                             }
  
300                             $successFlg = true;
  
301                         }
  
302                     }
  
303                 }
  
304             }
  
305         }
  
306
  
307         if ($successFlg) { return $data; }
  
308         return array();
  
309     }
  
310
  
311     /**
  
312      * 全部类型:获取某段时间内登录过的用户
  
313      * @param  int    $startTime 开始时间戳
  
314      * @param  int    $endTime   结束时间戳
  
315      * @return array
  
316      */
  
317     private function getSpecifyAllTimeRangeLogin($startTime, $endTime)
  
318     {
  
319         $count      = 0;       // 有效登录用户数
  
320         $list       = array(); // 有效登录用户
  
321         $successFlg = false;   // 查询到数据标识
  
322
  
323         // 未指定类型,直接对所有数据进行检索
  
324         // 获取需要查询的Key
  
325         $keyList = $this->_redisHandle->getSpecifyAllTimeRangeRedisKey($startTime, $endTime);
  
326
  
327         if (!empty($keyList)) {
  
328             foreach ($keyList as $keyListVal) {
  
329
  
330                 // 查询Kye
  
331                 $standardKeyList = $this->_redisHandle->getKeys($keyListVal['key']);
  
332
  
333                 if (is_array($standardKeyList) && count($standardKeyList) > 0) {
  
334
  
335                     // 查询到Key,直接读取数据,记录类型
  
336                     foreach ($standardKeyList as $standardKeyListVal) {
  
337
  
338                         // 验证该用户在此时间段是否登录过
  
339                         $redisCheckLogin = $this->_redisHandle->getUserTimeRangeLogin($standardKeyListVal, $startTime, $endTime);
  
340                         if ($redisCheckLogin['hasLog']['count'] > 0) {
  
341
  
342                             // 同一个用户只需记录一次
  
343                             $uid = $this->_redisHandle->getLoginLogKeyInfo($standardKeyListVal, 'uid');
  
344                             if (!in_array($uid, $list)) {
  
345                                 $count++;
  
346                                 $list[] = $uid;
  
347                             }
  
348                             $successFlg = true;
  
349                         }
  
350                     }
  
351                 }
  
352
  
353                 // 无论Redis中存在不存在都要尝试从数据库中获取一遍数据,来补充Redis获取的数据,保证检索数据完整(Redis类型缺失可能导致)
  
354                 $dbResult = $this->_dbHandle->getTimeRangeLoginSuccessUser($keyListVal['time'], $startTime, $endTime);
  
355                 if (!empty($dbResult)) {
  
356                     foreach ($dbResult as $dbResultVal) {
  
357                         if (!in_array($dbResultVal, $list)) {
  
358                             $count++;
  
359                             $list[] = $dbResultVal;
  
360                         }
  
361                     }
  
362                     $successFlg = true;
  
363                 }
  
364             }
  
365         }
  
366
  
367         if ($successFlg) {
  
368             return array(
  
369                 'all' => array(
  
370                     'count' => $count,
  
371                     'list'  => $list
  
372                 )
  
373             );
  
374         }
  
375         return array();
  
376     }
  
377
  
378     /**
  
379      * 验证开始结束时间
  
380      * @param  string $startTime 开始时间
  
381      * @param  string $endTime   结束时间
  
382      * @return boolean
  
383      */
  
384     private function checkTimeRange($startTime, $endTime)
  
385     {
  
386         return $this->_redisHandle->checkTimeRange($startTime, $endTime);
  
387     }
  
388
  
389     /**
  
390      * 批量验证用户类型
  
391      * @param  array  $typeArr 用户类型数组
  
392      * @return boolean
  
393      */
  
394     private function checkTypeArr($typeArr)
  
395     {
  
396         $flg = false;
  
397         if (is_array($typeArr) && !empty($typeArr)) {
  
398             foreach ($typeArr as $val) {
  
399                 if ($this->_redisHandle->checkType($val)) {
  
400                     $flg = true;
  
401                 } else {
  
402                     $flg = false; break;
  
403                 }
  
404             }
  
405         }
  
406         return $flg;
  
407     }
  
408
  
409     /**
  
410      * 定时任务每周调用一次:从Redis同步登录日志到数据库
  
411      * @param  int    $existsDay 一条记录在Redis中过期时间,单位:天,必须大于31
  
412      * @return string
  
413      * 'null':   Redis中无数据
  
414      * 'fail':   同步失败
  
415      * 'success':同步成功
  
416      */
  
417     public function cronWeeklySync($existsDay)
  
418     {
  
419
  
420         // 验证生存时间
  
421         if ($this->_redisHandle->checkExistsDay($existsDay)) {
  
422             $likeKey = 'loginLog_*';
  
423             $keyList = $this->_redisHandle->getKeys($likeKey);
  
424
  
425             if (!empty($keyList)) {
  
426                 foreach ($keyList as $keyVal) {
  
427
  
428                     if ($this->_redisHandle->checkLoginLogKey($keyVal)) {
  
429                         $keyTime         = $this->_redisHandle->getLoginLogKeyInfo($keyVal, 'time');
  
430                         $thisMonth       = date('Y-m');
  
431                         $beforeMonth     = date('Y-m', strtotime('-1 month'));
  
432
  
433                         // 验证是否需要进行同步:
  
434                         // 1. 当前日期 >= 8号,对本月所有记录进行同步,不对本月之前的记录进行同步
  
435                         // 2. 当前日期 <  8号,对本月所有记录进行同步,对本月前一个月的记录进行同步,对本月前一个月之前的所有记录不进行同步
  
436                         if (date('j') >= 8) {
  
437
  
438                             // 只同步本月数据
  
439                             if ($thisMonth == $keyTime) {
  
440                                 $this->redis2db($keyVal);
  
441                             }
  
442                         } else {
  
443
  
444                             // 同步本月或本月前一个月数据
  
445                             if ($thisMonth == $keyTime || $beforeMonth == $keyTime) {
  
446                                 $this->redis2db($keyVal);
  
447                             }
  
448                         }
  
449
  
450                         // 验证是否过期
  
451                         $existsSecond =  $existsDay * 24 * 60 * 60;
  
452                         if (strtotime($keyTime) + $existsSecond < time()) {
  
453
  
454                             // 过期删除
  
455                             $bitMap = $this->_redisHandle->getLoginLogBitMap($keyVal);
  
456                             Log::INFO('删除过期数据[' . $keyVal . ']:' . $bitMap);
  
457                             $this->_redisHandle->delLoginLog($keyVal);
  
458                         }
  
459                     }
  
460                 }
  
461                 return 'success';
  
462             }
  
463             return 'null';
  
464         }
  
465         return 'fail';
  
466     }
  
467
  
468     /**
  
469      * 将记录同步到数据库
  
470      * @param  string $key 记录Key
  
471      * @return boolean
  
472      */
  
473     private function redis2db($key)
  
474     {
  
475         if ($this->_redisHandle->checkLoginLogKey($key) && $this->_redisHandle->checkRedisLogExists($key)) {
  
476             $time = $this->_redisHandle->getLoginLogKeyInfo($key, 'time');
  
477             $data['id']      = Tools::generateId();
  
478             $data['user_id'] = $this->_redisHandle->getLoginLogKeyInfo($key, 'uid');
  
479             $data['type']    = $this->_redisHandle->getLoginLogKeyInfo($key, 'type');
  
480             $data['year']    = date('Y', strtotime($time));
  
481             $data['month']   = date('n', strtotime($time));
  
482             $data['bit_log'] = $this->_redisHandle->getLoginLogBitMap($key);
  
483             return $this->_dbHandle->redis2db($data);
  
484         }
  
485         return false;
  
486     }
  
487 }

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-426293-1-1.html 上篇帖子: Redis为什么使用单进程单线程方式也这么快 下篇帖子: Redis实现缓存,你应该懂的哪些思路!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表