基于Redis位图实现系统用户登录统计

项目需求,试着写了一个简单登录统计,基本功能都实现了,日志数据量小。具体性能没有进行测试~ 记录下开发过程与代码,留着以后改进!

需求

  • 实现记录用户哪天进行了登录,每天只记录是否登录过,重复登录状态算已登录。不需要记录用户的操作行为,不需要记录用户上次登录时间和IP地址(这部分以后需要可以单独拿出来存储)
  • 区分用户类型
  • 查询数据需要精确到天

分析

考虑到只是简单的记录用户是否登录,记录数据比较单一查询需要精确到天。以百万用户量为前提,前期考虑了几个方案

使用文件

使用单文件存储:文件占用空间增长速度快,海量数据检索不方便,Map/Reduce 操作也麻烦。

使用多文件存储:按日期对文件进行分割。每天记录当天日志,文件量过大。

使用数据库

不太认同直接使用数据库写入/读取

  • 频繁请求数据库做一些日志记录浪费服务器开销。
  • 随着时间推移数据急剧增大
  • 海量数据检索效率也不高,同时使用索引,易产生碎片,每次插入数据还要维护索引,影响性能

所以只考虑使用数据库做数据备份。

使用Redis位图(BitMap)

这也是在网上看到的方法,比较实用。也是我最终考虑使用的方法,

首先优点:

  • 数据量小:一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。我们知道8个bit可以组成一个Byte,所以bitmap本身会极大的节省储存空间。1亿人每天的登陆情况,用1亿bit,约1200WByte,约10M 的字符就能表示。

  • 计算方便:实用Redis bit 相关命令可以极大的简化一些统计操作。常用命令 SETBITGETBITBITCOUNTBITOP

再说弊端:

  • 存储单一:位图上存储只是0/1,所以需要存储其他信息就要别的地方单独记录,对于需要存储信息多的记录就需要使用别的方法了

设计

Redis BitMap

Key结构:前缀_年Y-月m_用户类型_用户ID

标准Key:KEYS loginLog_2017-10_client_1001
检索全部:KEYS loginLog_*
检索某年某月全部:KEYS loginLog_2017-10_*
检索单个用户全部:KEYS loginLog_*_client_1001
检索单个类型全部:KEYS loginLog_*_office_*
...

每条BitMap记录单个用户一个月的登录情况,一个bit位表示一天登录情况。

设置用户1001,217-10-25登录:SETBIT loginLog_2017-10_client_1001 25 1
获取用户1001,217-10-25是否登录:GETBIT loginLog_2017-10_client_1001 25
获取用户1001,217-10月是否登录:BITCOUNT loginLog_2017-10_client_1001
获取用户1001,217-10/9/7月是否登录:BITOP OR stat loginLog_2017-10_client_1001 loginLog_2017-09_client_1001 loginLog_2017-07_client_1001
...

关于获取登录信息,就得获取BitMap然后拆开,循环进行判断。特别涉及时间范围,需要注意时间边界的问题,不要查询出多余的数据

获取数据Redis优先级高于数据库,Redis有的记录不要去数据库获取

Redis数据过期:在数据同步中进行判断,过期时间自己定义(我定义的过期时间单位为“天”,必须大于31)。

在不能保证同步与过期一致性的问题,不要给Key设置过期时间,会造成数据丢失。

上一次更新时间:         2107-10-02
下一次更新时间:         2017-10-09
Redis BitMap 过期时间: 2017-10-05

这样会造成:2017-10-09同步的时候,3/4/5/6/7/8/9 数据丢失

所以我把Redis过期数据放到同步时进行判断  

我自己想的同步策略(定时每周一凌晨同步):

一、验证是否需要进行同步:
   1. 当前日期 >= 8号,对本月所有记录进行同步,不对本月之前的记录进行同步
   2. 当前日期 <  8号,对本月所有记录进行同步,对本月前一个月的记录进行同步,对本月前一个月之前的所有记录不进行同步
二、验证过期,如果过期,记录日志后删除

数据库,表结构

每周同步一次数据到数据库,表中一条数据对应一个BitMap,记录一个月数据。每次更新已存在的、插入没有的

暂定接口

  1.  设置用户登录
  2.  查询单个用户某天是否登录过
  3.     查询单个用户某月是否登录过
  4.  查询单个用户某个时间段是否登录过
  5.  查询单个用户某个时间段登录信息
  6.  指定用户类型:获取某个时间段内有效登录的用户
  7.  全部用户:获取某个时间段内有效登录的用户

Code

TP3中实现的代码,在接口服务器内部库中,Application\Lib\

  ├─LoginLog

  │ ├─Logs 日志目录,Redis中过期的记录删除写入日志进行备份

   ├─LoginLog.class.php 对外接口

  │ ├─LoginLogCommon.class.php 公共工具类

  │ ├─LoginLogDBHandle.class.php 数据库操作类

  │ ├─LoginLogRedisHandle.class.php Redis操作类

LoginLog.class.php

  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 class LoginLog extends HObject
 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 }
View Code

LoginLogCommon.class.php

  1 <?php
  2 
  3 namespace Lib\LoginLog;
  4 
  5 use Lib\RedisData;
  6 use Lib\Status;
  7 
  8 /**
  9  * 公共方法
 10  * User: dbn
 11  * Date: 2017/10/11
 12  * Time: 13:11
 13  */
 14 class LoginLogCommon
 15 {
 16     protected $_loginLog;
 17     protected $_redis;
 18 
 19     public function __construct(LoginLog $loginLog)
 20     {
 21         $this->_loginLog = $loginLog;
 22         $this->_redis    = RedisData::getRedis();
 23     }
 24 
 25     /**
 26      * 验证用户类型
 27      * @param  string $type 用户类型
 28      * @return boolean
 29      */
 30     protected function checkType($type)
 31     {
 32         if (in_array($type, array(
 33             Status::LOGIN_LOG_TYPE_ADMIN,
 34             Status::LOGIN_LOG_TYPE_CARRIER,
 35             Status::LOGIN_LOG_TYPE_DRIVER,
 36             Status::LOGIN_LOG_TYPE_OFFICE,
 37             Status::LOGIN_LOG_TYPE_CLIENT,
 38         ))) {
 39             return true;
 40         }
 41         $this->_loginLog->setError('未定义的日志类型:' . $type);
 42         return false;
 43     }
 44 
 45     /**
 46      * 验证唯一标识
 47      * @param  string  $uid
 48      * @return boolean
 49      */
 50     protected function checkUid($uid)
 51     {
 52         if (is_numeric($uid) && $uid > 0) {
 53             return true;
 54         }
 55         $this->_loginLog->setError('唯一标识非法:'  . $uid);
 56         return false;
 57     }
 58 
 59     /**
 60      * 验证时间戳
 61      * @param  string  $time
 62      * @return boolean
 63      */
 64     protected function checkTime($time)
 65     {
 66         if (is_numeric($time) && $time > 0) {
 67             return true;
 68         }
 69         $this->_loginLog->setError('时间戳非法:' . $time);
 70         return false;
 71     }
 72 
 73     /**
 74      * 验证时间是否在当月中
 75      * @param  string $time
 76      * @return boolean
 77      */
 78     protected function checkTimeWhetherThisMonth($time)
 79     {
 80         if ($this->checkTime($time) && $time > strtotime(date('Y-m')) && $time < strtotime(date('Y-m') . '-' . date('t'))) {
 81             return true;
 82         }
 83         $this->_loginLog->setError('时间未在当前月份中:' . $time);
 84         return false;
 85     }
 86 
 87     /**
 88      * 验证时间是否超过当前时间
 89      * @param  string $time
 90      * @return boolean
 91      */
 92     protected function checkTimeWhetherFutureTime($time)
 93     {
 94         if ($this->checkTime($time) && $time <= time()) {
 95             return true;
 96         }
 97         return false;
 98     }
 99 
100     /**
101      * 验证开始/结束时间
102      * @param  string $startTime 开始时间
103      * @param  string $endTime   结束时间
104      * @return boolean
105      */
106     protected function checkTimeRange($startTime, $endTime)
107     {
108         if ($this->checkTime($startTime) &&
109             $this->checkTime($endTime) &&
110             $startTime < $endTime &&
111             $startTime < time()
112         ) {
113             return true;
114         }
115         $this->_loginLog->setError('时间范围非法:' . $startTime . '-' . $endTime);
116         return false;
117     }
118 
119     /**
120      * 验证时间是否在指定范围内
121      * @param  string $time      需要检查的时间
122      * @param  string $startTime 开始时间
123      * @param  string $endTime   结束时间
124      * @return boolean
125      */
126     protected function checkTimeWithinTimeRange($time, $startTime, $endTime)
127     {
128         if ($this->checkTime($time) &&
129             $this->checkTimeRange($startTime, $endTime) &&
130             $startTime <= $time &&
131             $time <= $endTime
132         ) {
133             return true;
134         }
135         $this->_loginLog->setError('请求时间未在时间范围内:' . $time . '-' . $startTime . '-' . $endTime);
136         return false;
137     }
138 
139     /**
140      * 验证Redis日志记录标准Key
141      * @param  string  $key
142      * @return boolean
143      */
144     protected function checkLoginLogKey($key)
145     {
146         $pattern = '/^loginLog_\d{4}-\d{1,2}_\S+_\d+$/';
147         $result = preg_match($pattern, $key, $match);
148         if ($result > 0) {
149             return true;
150         }
151         $this->_loginLog->setError('RedisKey非法:' . $key);
152         return false;
153     }
154 
155     /**
156      * 获取月份中有多少天
157      * @param  int $time 时间戳
158      * @return int
159      */
160     protected function getDaysInMonth($time)
161     {
162         return date('t', $time);
163     }
164 
165     /**
166      * 对没有前导零的月份或日设置前导零
167      * @param  int $num 月份或日
168      * @return string
169      */
170     protected function setDateLeadingZero($num)
171     {
172         if (is_numeric($num) && strlen($num) <= 2) {
173             $num = (strlen($num) > 1 ? $num : '0' . $num);
174         }
175         return $num;
176     }
177 
178     /**
179      * 验证过期时间
180      * @param  int     $existsDay 一条记录在Redis中过期时间,单位:天,必须大于31
181      * @return boolean
182      */
183     protected function checkExistsDay($existsDay)
184     {
185         if (is_numeric($existsDay) && ctype_digit(strval($existsDay)) && $existsDay > 31) {
186             return true;
187         }
188         $this->_loginLog->setError('过期时间非法:' . $existsDay);
189         return false;
190     }
191 
192     /**
193      * 获取开始日期边界
194      * @param  int $time      需要判断的时间戳
195      * @param  int $startTime 起始时间
196      * @return int
197      */
198     protected function getStartTimeBorder($time, $startTime)
199     {
200         $initDay = 1;
201         if ($this->checkTime($time) && $this->checkTime($startTime) &&
202             date('Y-m', $time) === date('Y-m', $startTime) && false !== date('Y-m', $time)) {
203             $initDay = date('j', $startTime);
204         }
205         return $initDay;
206     }
207 
208     /**
209      * 获取结束日期边界
210      * @param  int $time      需要判断的时间戳
211      * @param  int $endTime   结束时间
212      * @return int
213      */
214     protected function getEndTimeBorder($time, $endTime)
215     {
216         $border = $this->getDaysInMonth($time);
217         if ($this->checkTime($time) && $this->checkTime($endTime) &&
218             date('Y-m', $time) === date('Y-m', $endTime) && false !== date('Y-m', $time)) {
219             $border = date('j', $endTime);
220         }
221         return $border;
222     }
223 }
View Code

LoginLogDBHandle.class.php

  1 <?php
  2 
  3 namespace Lib\LoginLog;
  4 use Think\Model;
  5 
  6 /**
  7  * 数据库登录日志处理类
  8  * User: dbn
  9  * Date: 2017/10/11
 10  * Time: 13:12
 11  */
 12 class LoginLogDBHandle extends LoginLogCommon
 13 {
 14 
 15     /**
 16      * 从数据库中获取用户某月记录在指定时间范围内的用户信息
 17      * @param  string  $type      用户类型
 18      * @param  int     $uid       唯一标识(用户ID)
 19      * @param  int     $time      需要查询月份时间戳
 20      * @param  int     $startTime 开始时间戳
 21      * @param  int     $endTime   结束时间戳
 22      * @return array
 23      * array(
 24      *      'hasLog' => array(
 25      *          'count' => n,                                  // 有效登录次数,每天重复登录算一次
 26      *          'list' => array('2017-10-1', '2017-10-15' ...) // 有效登录日期
 27      *      ),
 28      *      'notLog' => array(
 29      *          'count' => n,                                  // 未登录次数
 30      *          'list' => array('2017-10-1', '2017-10-15' ...) // 未登录日期
 31      *      )
 32      * )
 33      */
 34     public function getUserTimeRangeLogin($type, $uid, $time, $startTime, $endTime)
 35     {
 36         $hasCount = 0;       // 有效登录次数
 37         $notCount = 0;       // 未登录次数
 38         $hasList  = array(); // 有效登录日期
 39         $notList  = array(); // 未登录日期
 40 
 41         if ($this->checkType($type) && $this->checkUid($uid) && $this->checkTimeWithinTimeRange($time, $startTime, $endTime)) {
 42 
 43             $timeYM = date('Y-m', $time);
 44 
 45             // 设置开始时间
 46             $initDay = $this->getStartTimeBorder($time, $startTime);
 47 
 48             // 设置结束时间
 49             $border = $this->getEndTimeBorder($time, $endTime);
 50 
 51             $bitMap = $this->getBitMapFind($type, $uid, date('Y', $time), date('n', $time));
 52             for ($i = $initDay; $i <= $border; $i++) {
 53 
 54                 if (!empty($bitMap)) {
 55                     if ($bitMap[$i-1] == '1') {
 56                         $hasCount++;
 57                         $hasList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
 58                     } else {
 59                         $notCount++;
 60                         $notList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
 61                     }
 62                 } else {
 63                     $notCount++;
 64                     $notList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
 65                 }
 66             }
 67         }
 68 
 69         return array(
 70             'hasLog' => array(
 71                 'count' => $hasCount,
 72                 'list'  => $hasList
 73             ),
 74             'notLog' => array(
 75                 'count' => $notCount,
 76                 'list'  => $notList
 77             )
 78         );
 79     }
 80 
 81     /**
 82      * 从数据库获取用户某月日志位图
 83      * @param  string  $type  用户类型
 84      * @param  int     $uid   唯一标识(用户ID)
 85      * @param  int     $year  年Y
 86      * @param  int     $month 月n
 87      * @return string
 88      */
 89     private function getBitMapFind($type, $uid, $year, $month)
 90     {
 91         $model = D('Home/StatLoginLog');
 92         $map['type']    = array('EQ', $type);
 93         $map['user_id'] = array('EQ', $uid);
 94         $map['year']    = array('EQ', $year);
 95         $map['month']   = array('EQ', $month);
 96 
 97         $result = $model->field('bit_log')->where($map)->find();
 98         if (false !== $result && isset($result['bit_log']) && !empty($result['bit_log'])) {
 99             return $result['bit_log'];
100         }
101         return '';
102     }
103 
104     /**
105      * 从数据库中判断用户在某一天是否登录过
106      * @param  string  $type  用户类型
107      * @param  int     $uid   唯一标识(用户ID)
108      * @param  int     $time  时间戳
109      * @return boolean 参数错误或未登录过返回false,登录过返回true
110      */
111     public function dateWhetherLogin($type, $uid, $time)
112     {
113         if ($this->checkType($type) && $this->checkUid($uid) && $this->checkTime($time)) {
114 
115             $timeInfo = getdate($time);
116             $bitMap = $this->getBitMapFind($type, $uid, $timeInfo['year'], $timeInfo['mon']);
117             if (!empty($bitMap)) {
118                 if ($bitMap[$timeInfo['mday']-1] == '1') {
119                     return true;
120                 }
121             }
122         }
123         return false;
124     }
125 
126     /**
127      * 从数据库中判断用户在某月是否登录过
128      * @param  string  $type  用户类型
129      * @param  int     $uid   唯一标识(用户ID)
130      * @param  int     $time  时间戳
131      * @return boolean 参数错误或未登录过返回false,登录过返回true
132      */
133     public function dateMonthWhetherLogin($type, $uid, $time)
134     {
135         if ($this->checkType($type) && $this->checkUid($uid) && $this->checkTime($time)) {
136 
137             $timeInfo = getdate($time);
138             $userArr = $this->getMonthLoginSuccessUser($timeInfo['year'], $timeInfo['mon'], $type);
139             if (!empty($userArr)) {
140                 if (in_array($uid, $userArr)) {
141                     return true;
142                 }
143             }
144         }
145         return false;
146     }
147 
148     /**
149      * 获取某月所有有效登录过的用户ID
150      * @param  int     $year  年Y
151      * @param  int     $month 月n
152      * @param  string  $type  用户类型,为空时获取全部类型
153      * @return array
154      */
155     public function getMonthLoginSuccessUser($year, $month, $type = '')
156     {
157         $data = array();
158         if (is_numeric($year) && is_numeric($month)) {
159             $model = D('Home/StatLoginLog');
160             $map['year']    = array('EQ', $year);
161             $map['month']   = array('EQ', $month);
162             $map['bit_log'] = array('LIKE', '%1%');
163             if ($type != '' && $this->checkType($type)) {
164                 $map['type']    = array('EQ', $type);
165             }
166             $result = $model->field('user_id')->where($map)->select();
167             if (false !== $result && count($result) > 0) {
168                 foreach ($result as $val) {
169                     if (isset($val['user_id'])) {
170                         $data[] = $val['user_id'];
171                     }
172                 }
173             }
174         }
175         return $data;
176     }
177 
178     /**
179      * 从数据库中获取某月所有记录在指定时间范围内的用户ID
180      * @param  int     $time      查询的时间戳
181      * @param  int     $startTime 开始时间戳
182      * @param  int     $endTime   结束时间戳
183      * @param  string  $type  用户类型,为空时获取全部类型
184      * @return array
185      */
186     public function getTimeRangeLoginSuccessUser($time, $startTime, $endTime, $type = '')
187     {
188         $data = array();
189         if ($this->checkTimeWithinTimeRange($time, $startTime, $endTime)) {
190 
191             $timeInfo = getdate($time);
192 
193             // 获取满足时间条件的记录
194             $model = D('Home/StatLoginLog');
195             $map['year']    = array('EQ', $timeInfo['year']);
196             $map['month']   = array('EQ', $timeInfo['mon']);
197             if ($type != '' && $this->checkType($type)) {
198                 $map['type']    = array('EQ', $type);
199             }
200 
201             $result = $model->where($map)->select();
202             if (false !== $result && count($result) > 0) {
203 
204                 // 设置开始时间
205                 $initDay = $this->getStartTimeBorder($time, $startTime);
206 
207                 // 设置结束时间
208                 $border = $this->getEndTimeBorder($time, $endTime);
209 
210                 foreach ($result as $val) {
211 
212                     $bitMap = $val['bit_log'];
213                     for ($i = $initDay; $i <= $border; $i++) {
214 
215                         if ($bitMap[$i-1] == '1' && !in_array($val['user_id'], $data)) {
216                             $data[] = $val['user_id'];
217                         }
218                     }
219                 }
220             }
221         }
222         return $data;
223     }
224 
225     /**
226      * 将数据更新到数据库
227      * @param  array $data 单条记录的数据
228      * @return boolean
229      */
230     public function redis2db($data)
231     {
232         $model = D('Home/StatLoginLog');
233 
234         // 验证记录是否存在
235         $map['user_id'] = array('EQ', $data['user_id']);
236         $map['type']    = array('EQ', $data['type']);
237         $map['year']    = array('EQ', $data['year']);
238         $map['month']   = array('EQ', $data['month']);
239 
240         $count = $model->where($map)->count();
241         if (false !== $count && $count > 0) {
242 
243             // 存在记录进行更新
244             $saveData['bit_log'] = $data['bit_log'];
245 
246             if (!$model->create($saveData, Model::MODEL_UPDATE)) {
247 
248                 $this->_loginLog->setError('同步登录日志-更新记录,创建数据对象失败:' . $model->getError());
249                 logger()->error('同步登录日志-更新记录,创建数据对象失败:' . $model->getError());
250                 return false;
251             } else {
252 
253                 $result = $model->where($map)->save();
254 
255                 if (false !== $result) {
256                     return true;
257                 } else {
258                     $this->_loginLog->setError('同步登录日志-更新记录,更新数据失败:' . json_encode($data));
259                     logger()->error('同步登录日志-更新记录,更新数据失败:' . json_encode($data));
260                     return false;
261                 }
262             }
263         } else {
264 
265             // 不存在记录插入一条新的记录
266             if (!$model->create($data, Model::MODEL_INSERT)) {
267 
268                 $this->_loginLog->setError('同步登录日志-插入记录,创建数据对象失败:' . $model->getError());
269                 logger()->error('同步登录日志-插入记录,创建数据对象失败:' . $model->getError());
270                 return false;
271             } else {
272 
273                 $result = $model->add();
274 
275                 if (false !== $result) {
276                     return true;
277                 } else {
278                     $this->_loginLog->setError('同步登录日志-插入记录,插入数据失败:' . json_encode($data));
279                     logger()->error('同步登录日志-插入记录,插入数据失败:' . json_encode($data));
280                     return false;
281                 }
282             }
283         }
284     }
285 }
View Code

LoginLogRedisHandle.class.php

  1 <?php
  2 
  3 namespace Lib\LoginLog;
  4 
  5 /**
  6  * Redis登录日志处理类
  7  * User: dbn
  8  * Date: 2017/10/11
  9  * Time: 15:53
 10  */
 11 class LoginLogRedisHandle extends LoginLogCommon
 12 {
 13     /**
 14      * 记录登录:每天只记录一次登录,只允许设置当月内登录记录
 15      * @param  string $key  日志记录Key
 16      * @param  int    $time 时间戳
 17      * @return boolean
 18      */
 19     public function setLogging($key, $time)
 20     {
 21         if ($this->checkLoginLogKey($key) && $this->checkTimeWhetherThisMonth($time)) {
 22 
 23             // 判断用户当天是否已经登录过
 24             $whetherLoginResult = $this->dateWhetherLogin($key, $time);
 25             if (!$whetherLoginResult) {
 26 
 27                 // 当天未登录,记录登录
 28                 $this->_redis->setBit($key, date('d', $time), 1);
 29             }
 30             return true;
 31         }
 32         return false;
 33     }
 34 
 35     /**
 36      * 从Redis中判断用户在某一天是否登录过
 37      * @param  string $key  日志记录Key
 38      * @param  int    $time 时间戳
 39      * @return boolean 参数错误或未登录过返回false,登录过返回true
 40      */
 41     public function dateWhetherLogin($key, $time)
 42     {
 43         if ($this->checkLoginLogKey($key) && $this->checkTime($time)) {
 44             $result = $this->_redis->getBit($key, date('d', $time));
 45             if ($result === 1) {
 46                 return true;
 47             }
 48         }
 49         return false;
 50     }
 51 
 52     /**
 53      * 从Redis中判断用户在某月是否登录过
 54      * @param  string $key  日志记录Key
 55      * @return boolean 参数错误或未登录过返回false,登录过返回true
 56      */
 57     public function dateMonthWhetherLogin($key)
 58     {
 59         if ($this->checkLoginLogKey($key)) {
 60             $result = $this->_redis->bitCount($key);
 61             if ($result > 0) {
 62                 return true;
 63             }
 64         }
 65         return false;
 66     }
 67 
 68     /**
 69      * 判断某月登录记录在Redis中是否存在
 70      * @param  string  $key  日志记录Key
 71      * @return boolean
 72      */
 73     public function checkRedisLogExists($key)
 74     {
 75         if ($this->checkLoginLogKey($key)) {
 76             if ($this->_redis->exists($key)) {
 77                 return true;
 78             }
 79         }
 80         return false;
 81     }
 82 
 83     /**
 84      * 从Redis中获取用户某月记录在指定时间范围内的用户信息
 85      * @param  string  $key       日志记录Key
 86      * @param  int     $startTime 开始时间戳
 87      * @param  int     $endTime   结束时间戳
 88      * @return array
 89      * array(
 90      *      'hasLog' => array(
 91      *          'count' => n,                                  // 有效登录次数,每天重复登录算一次
 92      *          'list' => array('2017-10-1', '2017-10-15' ...) // 有效登录日期
 93      *      ),
 94      *      'notLog' => array(
 95      *          'count' => n,                                  // 未登录次数
 96      *          'list' => array('2017-10-1', '2017-10-15' ...) // 未登录日期
 97      *      )
 98      * )
 99      */
100     public function getUserTimeRangeLogin($key, $startTime, $endTime)
101     {
102         $hasCount = 0;       // 有效登录次数
103         $notCount = 0;       // 未登录次数
104         $hasList  = array(); // 有效登录日期
105         $notList  = array(); // 未登录日期
106 
107         if ($this->checkLoginLogKey($key) && $this->checkTimeRange($startTime, $endTime) && $this->checkRedisLogExists($key)) {
108 
109             $keyTime = $this->getLoginLogKeyInfo($key, 'time');
110             $keyTime = strtotime($keyTime);
111             $timeYM  = date('Y-m', $keyTime);
112 
113             // 设置开始时间
114             $initDay = $this->getStartTimeBorder($keyTime, $startTime);
115 
116             // 设置结束时间
117             $border = $this->getEndTimeBorder($keyTime, $endTime);
118 
119             for ($i = $initDay; $i <= $border; $i++) {
120                 $result = $this->_redis->getBit($key, $i);
121                 if ($result === 1) {
122                     $hasCount++;
123                     $hasList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
124                 } else {
125                     $notCount++;
126                     $notList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
127                 }
128             }
129         }
130 
131         return array(
132             'hasLog' => array(
133                 'count' => $hasCount,
134                 'list'  => $hasList
135             ),
136             'notLog' => array(
137                 'count' => $notCount,
138                 'list'  => $notList
139             )
140         );
141     }
142 
143     /**
144      * 面向用户:获取时间范围内可能需要的Key
145      * @param  string $type      用户类型
146      * @param  int    $uid       唯一标识(用户ID)
147      * @param  string $startTime 开始时间
148      * @param  string $endTime   结束时间
149      * @return array
150      */
151     public function getTimeRangeRedisKey($type, $uid, $startTime, $endTime)
152     {
153         $list = array();
154 
155         if ($this->checkType($type) && $this->checkUid($uid) && $this->checkTimeRange($startTime, $endTime)) {
156 
157             $data = $this->getSpecifyUserKeyHandle($type, $uid, $startTime);
158             if (!empty($data)) { $list[] = $data; }
159 
160             $temYM  = strtotime('+1 month', strtotime(date('Y-m', $startTime)));
161 
162             while ($temYM <= $endTime) {
163                 $data = $this->getSpecifyUserKeyHandle($type, $uid, $temYM);
164                 if (!empty($data)) { $list[] = $data; }
165 
166                 $temYM  = strtotime('+1 month', $temYM);
167             }
168         }
169         return $list;
170     }
171     private function getSpecifyUserKeyHandle($type, $uid, $time)
172     {
173         $data = array();
174         $key = $this->getLoginLogKey($type, $uid, $time);
175         if ($this->checkLoginLogKey($key)) {
176             $data = array(
177                 'key'  => $key,
178                 'time' => $time
179             );
180         }
181         return $data;
182     }
183 
184     /**
185      * 面向类型:获取时间范围内可能需要的Key
186      * @param  string $type      用户类型
187      * @param  string $startTime 开始时间
188      * @param  string $endTime   结束时间
189      * @return array
190      */
191     public function getSpecifyTypeTimeRangeRedisKey($type, $startTime, $endTime)
192     {
193         $list = array();
194 
195         if ($this->checkType($type) && $this->checkTimeRange($startTime, $endTime)) {
196 
197             $data = $this->getSpecifyTypeKeyHandle($type, $startTime);
198             if (!empty($data)) { $list[] = $data; }
199 
200             $temYM  = strtotime('+1 month', strtotime(date('Y-m', $startTime)));
201 
202             while ($temYM <= $endTime) {
203                 $data = $this->getSpecifyTypeKeyHandle($type, $temYM);
204                 if (!empty($data)) { $list[] = $data; }
205 
206                 $temYM  = strtotime('+1 month', $temYM);
207             }
208         }
209         return $list;
210     }
211     private function getSpecifyTypeKeyHandle($type, $time)
212     {
213         $data = array();
214         $temUid = '11111111';
215 
216         $key = $this->getLoginLogKey($type, $temUid, $time);
217         if ($this->checkLoginLogKey($key)) {
218             $arr = explode('_', $key);
219             $arr[count($arr)-1] = '*';
220             $key = implode('_', $arr);
221             $data = array(
222                 'key'  => $key,
223                 'time' => $time
224             );
225         }
226         return $data;
227     }
228 
229     /**
230      * 面向全部:获取时间范围内可能需要的Key
231      * @param  string $startTime 开始时间
232      * @param  string $endTime   结束时间
233      * @return array
234      */
235     public function getSpecifyAllTimeRangeRedisKey($startTime, $endTime)
236     {
237         $list = array();
238 
239         if ($this->checkTimeRange($startTime, $endTime)) {
240 
241             $data = $this->getSpecifyAllKeyHandle($startTime);
242             if (!empty($data)) { $list[] = $data; }
243 
244             $temYM  = strtotime('+1 month', strtotime(date('Y-m', $startTime)));
245 
246             while ($temYM <= $endTime) {
247                 $data = $this->getSpecifyAllKeyHandle($temYM);
248                 if (!empty($data)) { $list[] = $data; }
249 
250                 $temYM  = strtotime('+1 month', $temYM);
251             }
252         }
253         return $list;
254     }
255     private function getSpecifyAllKeyHandle($time)
256     {
257         $data = array();
258         $temUid  = '11111111';
259         $temType = 'office';
260 
261         $key = $this->getLoginLogKey($temType, $temUid, $time);
262         if ($this->checkLoginLogKey($key)) {
263             $arr = explode('_', $key);
264             array_pop($arr);
265             $arr[count($arr)-1] = '*';
266             $key = implode('_', $arr);
267             $data = array(
268                 'key'  => $key,
269                 'time' => $time
270             );
271         }
272         return $data;
273     }
274 
275     /**
276      * 从Redis中查询满足条件的Key
277      * @param  string $key 查询的Key
278      * @return array
279      */
280     public function getKeys($key)
281     {
282         return $this->_redis->keys($key);
283     }
284 
285     /**
286      * 从Redis中删除记录
287      * @param  string $key 记录的Key
288      * @return boolean
289      */
290     public function delLoginLog($key)
291     {
292         return $this->_redis->del($key);
293     }
294 
295     /**
296      * 获取日志标准Key:前缀_年-月_用户类型_唯一标识
297      * @param  string $type 用户类型
298      * @param  int    $uid  唯一标识(用户ID)
299      * @param  int    $time 时间戳
300      * @return string
301      */
302     public function getLoginLogKey($type, $uid, $time)
303     {
304         if ($this->checkType($type) && $this->checkUid($uid) && $this->checkTime($time)) {
305             return 'loginLog_' . date('Y-m', $time) . '_' . $type . '_' . $uid;
306         }
307         return '';
308     }
309 
310     /**
311      * 获取日志标准Key上信息
312      * @param  string $key   key
313      * @param  string $field 需要的参数 time,type,uid
314      * @return mixed 返回对应的值,没有返回null
315      */
316     public function getLoginLogKeyInfo($key, $field)
317     {
318         $param = array();
319         if ($this->checkLoginLogKey($key)) {
320             $arr = explode('_', $key);
321             $param['time'] = $arr[1];
322             $param['type'] = $arr[2];
323             $param['uid']  = $arr[3];
324         }
325         return $param[$field];
326     }
327 
328     /**
329      * 获取Key记录的登录位图
330      * @param  string $key key
331      * @return string
332      */
333     public function getLoginLogBitMap($key)
334     {
335         $bitMap = '';
336         if ($this->checkLoginLogKey($key)) {
337             $time = $this->getLoginLogKeyInfo($key, 'time');
338             $maxDay = $this->getDaysInMonth(strtotime($time));
339             for ($i = 1; $i <= $maxDay; $i++) {
340                 $bitMap .= $this->_redis->getBit($key, $i);
341             }
342         }
343         return $bitMap;
344     }
345 
346     /**
347      * 验证日志标准Key
348      * @param  string $key
349      * @return boolean
350      */
351     public function checkLoginLogKey($key)
352     {
353         return parent::checkLoginLogKey($key);
354     }
355 
356     /**
357      * 验证开始/结束时间
358      * @param  string $startTime 开始时间
359      * @param  string $endTime   结束时间
360      * @return boolean
361      */
362     public function checkTimeRange($startTime, $endTime)
363     {
364         return parent::checkTimeRange($startTime, $endTime);
365     }
366 
367     /**
368      * 验证用户类型
369      * @param  string $type
370      * @return boolean
371      */
372     public function checkType($type)
373     {
374         return parent::checkType($type);
375     }
376 
377     /**
378      * 验证过期时间
379      * @param  int $existsDay 一条记录在Redis中过期时间,单位:天,必须大于31
380      * @return boolean
381      */
382     public function checkExistsDay($existsDay)
383     {
384         return parent::checkExistsDay($existsDay);
385     }
386 }
View Code

参考资料

https://segmentfault.com/a/1190000008188655

http://blog.csdn.net/rdhj5566/article/details/54313840

http://www.redis.net.cn/tutorial/3508.html

posted @ 2017-10-16 17:06  BNDong  阅读(10321)  评论(3编辑  收藏  举报