基於Redis位圖實現系統用戶登陸統計

項目需求,試着寫了一個簡單登陸統計,基本功能都實現了,日誌數據量小。具體性能沒有進行測試~ 記錄下開發過程與代碼,留着之後改進!

需求

  1.  實現記錄用戶哪天進行了登陸,天天只記錄是否登陸過,重複登陸狀態算已登陸。不須要記錄用戶的操做行爲,不須要記錄用戶上次登陸時間和IP地址(這部分之後須要能夠單獨拿出來存儲)
  2.  區分用戶類型
  3.  查詢數據須要精確到天

分析

考慮到只是簡單的記錄用戶是否登陸,記錄數據比較單一查詢須要精確到天。以百萬用戶量爲前提,前期考慮了幾個方案php

使用文件

使用單文件存儲:文件佔用空間增加速度快,海量數據檢索不方便,Map/Reduce操做也麻煩html

使用多文件存儲:按日期對文件進行分割。天天記錄當天日誌,文件量過大git

使用數據庫

不太認同直接使用數據庫寫入/讀取redis

  1.  頻繁請求數據庫作一些日誌記錄浪費服務器開銷。
  2.  隨着時間推移數據急劇增大
  3.  海量數據檢索效率也不高,同時使用索引,易產生碎片,每次插入數據還要維護索引,影響性能

因此只考慮使用數據庫作數據備份。數據庫

使用Redis位圖(BitMap)

這也是在網上看到的方法,比較實用。也是我最終考慮使用的方法,json

首先優勢:segmentfault

數據量小:一個bit位來表示某個元素對應的值或者狀態,其中的key就是對應元素自己。咱們知道8個bit能夠組成一個Byte,因此bitmap自己會極大的節省儲存空間。1億人天天的登錄狀況,用1億bit,約1200WByte,約10M 的字符就能表示。數組

計算方便:實用Redis bit 相關命令能夠極大的簡化一些統計操做。經常使用命令 SETBITGETBITBITCOUNTBITOP服務器

再說弊端:ide

存儲單一:這也算不上什麼缺點,位圖上存儲只是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、驗證是否須要進行同步:
   1. 當前日期 >= 8號,對本月全部記錄進行同步,不對本月以前的記錄進行同步
   2. 當前日期 <  8號,對本月全部記錄進行同步,對本月前一個月的記錄進行同步,對本月前一個月以前的全部記錄不進行同步
2、驗證過時,若是過時,記錄日誌後刪除

數據庫,表結構

每週同步一次數據到數據庫,表中一條數據對應一個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

參考資料

http://www.javashuo.com/article/p-pgugovbw-gc.html

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

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

相關文章
相關標籤/搜索