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


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

    1. 需求

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

    2. 分析

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

    2.1 使用文件

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

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

    2.2 使用数据库

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

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

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

    2.3 使用Redis位图(BitMap)

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

      首先优点:

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

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

      再说弊端:

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

    3. 设计

    3.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月是否登录: GETCOUNT 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号,对本月所有记录进行同步,对本月前一个月的记录进行同步,对本月前一个月之前的所有记录不进行同步
    二、验证过期,如果过期,记录日志后删除

    3.2  数据库,表结构

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

    3.3 暂定接口

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

    4. Code

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

      ├─LoginLog

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

       ├─LoginLog.class.php 对外接口

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

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

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

    4.1 LoginLog.class.php

      1 <?php
      2 
      3 namespace LibLoginLog;
      4 use LibCLogFileHandler;
      5 use LibHObject;
      6 use LibLog;
      7 use LibTools;
      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

    4.2 LoginLogCommon.class.php

      1 <?php
      2 
      3 namespace LibLoginLog;
      4 
      5 use LibRedisData;
      6 use LibStatus;
      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

    4.3 LoginLogDBHandle.class.php

      1 <?php
      2 
      3 namespace LibLoginLog;
      4 use ThinkModel;
      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

    4.4 LoginLogRedisHandle.class.php

      1 <?php
      2 
      3 namespace LibLoginLog;
      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

    5. 参考资料

      https://segmentfault.com/a/1190000008188655

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

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

  • 相关阅读:
    I00038 自守数(Automorphic number)
    I00036 盈数(Abundant number)
    I00036 盈数(Abundant number)
    I00037 亏数(Deficient number)
    I00037 亏数(Deficient number)
    I00035 完美数(Perfect number)
    I00035 完美数(Perfect number)
    I00034 累加与累乘
    I00034 累加与累乘
    codeforces589J 简单dfs,队列
  • 原文地址:https://www.cnblogs.com/ghjbk/p/7681724.html
Copyright © 2020-2023  润新知