婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > 基于Redis位圖實現系統用戶登錄統計

基于Redis位圖實現系統用戶登錄統計

熱門標簽:十堰營銷電銷機器人哪家便宜 日本中國地圖標注 山東外呼銷售系統招商 北京400電話辦理收費標準 宿遷便宜外呼系統平臺 超呼電話機器人 貴州電銷卡外呼系統 魔獸2青云地圖標注 鄭州人工智能電銷機器人系統

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

1. 需求 

         實現記錄用戶哪天進行了登錄,每天只記錄是否登錄過,重復登錄狀態算已登錄。不需要記錄用戶的操作行為,不需要記錄用戶上次登錄時間和IP地址(這部分以后需要可以單獨拿出來存儲) 區分用戶類型 查詢數據需要精確到天

2. 分析

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

2.1 使用文件

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

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

2.2 使用數據庫

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

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

  所以只考慮使用數據庫做數據備份。

2.3 使用Redis位圖(BitMap)

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

  首先優點:

  數據量小:一個bit位來表示某個元素對應的值或者狀態,其中的key就是對應元素本身。我們知道8個bit可以組成一個Byte,所以bitmap本身會極大的節省儲存空間。1億人每天的登陸情況,用1億bit,約1200WByte,約10M 的字符就能表示。

  計算方便:實用Redis bit 相關命令可以極大的簡化一些統計操作。常用命令 SETBIT、GETBIT、BITCOUNT、BITOP

  再說弊端:

  存儲單一:這也算不上什么缺點,位圖上存儲只是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號,對本月所有記錄進行同步,對本月前一個月的記錄進行同步,對本月前一個月之前的所有記錄不進行同步

二、驗證過期,如果過期,記錄日志后刪除[/code]3.2 數據庫,表結構

  每周同步一次數據到數據庫,表中一條數據對應一個BitMap,記錄一個月數據。每次更新已存在的、插入沒有的

3.3 暫定接口 

  •  設置用戶登錄
  •  查詢單個用戶某天是否登錄過
  • 查詢單個用戶某月是否登錄過
  •  查詢單個用戶某個時間段是否登錄過
  •  查詢單個用戶某個時間段登錄信息
  •  指定用戶類型:獲取某個時間段內有效登錄的用戶
  •  全部用戶:獲取某個時間段內有效登錄的用戶

4. Code

  TP3中實現的代碼,在接口服務器內部庫中,Application\Lib\

  ├─LoginLog

  │├─Logs 日志目錄,Redis中過期的記錄刪除寫入日志進行備份

  │├─LoginLog.class.php 對外接口

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

  │├─LoginLogDBHandle.class.php 數據庫操作類

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

4.1 LoginLog.class.php

?php

namespace Lib\LoginLog;
use Lib\CLogFileHandler;
use Lib\HObject;
use Lib\Log;
use Lib\Tools;

/**
* 登錄日志操作類
* User: dbn
* Date: 2017/10/11
* Time: 12:01
* ------------------------
* 日志最小粒度為:天
*/

class LoginLog extends HObject
{
private $_redisHandle; // Redis登錄日志處理
private $_dbHandle;  // 數據庫登錄日志處理

public function __construct()
{
$this->_redisHandle = new LoginLogRedisHandle($this);
$this->_dbHandle  = new LoginLogDBHandle($this);

// 初始化日志
$logHandler = new CLogFileHandler(__DIR__ . '/Logs/del.log');
Log::Init($logHandler, 15);
}

/**
* 記錄登錄:每天只記錄一次登錄,只允許設置當月內登錄記錄
* @param string $type 用戶類型
* @param int  $uid 唯一標識(用戶ID)
* @param int  $time 時間戳
* @return boolean
*/
public function setLogging($type, $uid, $time)
{
$key = $this->_redisHandle->getLoginLogKey($type, $uid, $time);
if ($this->_redisHandle->checkLoginLogKey($key)) {
return $this->_redisHandle->setLogging($key, $time);
}
return false;
}

/**
* 查詢用戶某一天是否登錄過
* @param string $type 用戶類型
* @param int  $uid 唯一標識(用戶ID)
* @param int  $time 時間戳
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function getDateWhetherLogin($type, $uid, $time)
{
$key = $this->_redisHandle->getLoginLogKey($type, $uid, $time);
if ($this->_redisHandle->checkLoginLogKey($key)) {

// 判斷Redis中是否存在記錄
$isRedisExists = $this->_redisHandle->checkRedisLogExists($key);
if ($isRedisExists) {

// 從Redis中進行判斷
return $this->_redisHandle->dateWhetherLogin($key, $time);
} else {

// 從數據庫中進行判斷
return $this->_dbHandle->dateWhetherLogin($type, $uid, $time);
}
}
return false;
}

/**
* 查詢用戶某月是否登錄過
* @param string $type 用戶類型
* @param int  $uid 唯一標識(用戶ID)
* @param int  $time 時間戳
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function getDateMonthWhetherLogin($type, $uid, $time)
{
$key = $this->_redisHandle->getLoginLogKey($type, $uid, $time);
if ($this->_redisHandle->checkLoginLogKey($key)) {

// 判斷Redis中是否存在記錄
$isRedisExists = $this->_redisHandle->checkRedisLogExists($key);
if ($isRedisExists) {

// 從Redis中進行判斷
return $this->_redisHandle->dateMonthWhetherLogin($key);
} else {

// 從數據庫中進行判斷
return $this->_dbHandle->dateMonthWhetherLogin($type, $uid, $time);
}
}
return false;
}

/**
* 查詢用戶在某個時間段是否登錄過
* @param string $type 用戶類型
* @param int  $uid 唯一標識(用戶ID)
* @param int  $startTime 開始時間戳
* @param int  $endTime  結束時間戳
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function getTimeRangeWhetherLogin($type, $uid, $startTime, $endTime){
$result = $this->getUserTimeRangeLogin($type, $uid, $startTime, $endTime);
if ($result['hasLog']['count'] > 0) {
return true;
}
return false;
}

/**
* 獲取用戶某時間段內登錄信息
* @param string $type   用戶類型
* @param int  $uid    唯一標識(用戶ID)
* @param int  $startTime 開始時間戳
* @param int  $endTime  結束時間戳
* @return array 參數錯誤或未查詢到返回array()
* -------------------------------------------------
* 查詢到結果:
* array(
*   'hasLog' => array(
*     'count' => n,                 // 有效登錄次數,每天重復登錄算一次
*     'list' => array('2017-10-1', '2017-10-15' ...) // 有效登錄日期
*   ),
*   'notLog' => array(
*     'count' => n,                 // 未登錄次數
*     'list' => array('2017-10-1', '2017-10-15' ...) // 未登錄日期
*   )
* )
*/
public function getUserTimeRangeLogin($type, $uid, $startTime, $endTime)
{
$hasCount  = 0;    // 有效登錄次數
$notCount  = 0;    // 未登錄次數
$hasList  = array(); // 有效登錄日期
$notList  = array(); // 未登錄日期
$successFlg = false;  // 查詢到數據標識

if ($this->checkTimeRange($startTime, $endTime)) {

// 獲取需要查詢的Key
$keyList = $this->_redisHandle->getTimeRangeRedisKey($type, $uid, $startTime, $endTime);

if (!empty($keyList)) {
foreach ($keyList as $key => $val) {

// 判斷Redis中是否存在記錄
$isRedisExists = $this->_redisHandle->checkRedisLogExists($val['key']);
if ($isRedisExists) {

// 存在,直接從Redis中獲取
$logInfo = $this->_redisHandle->getUserTimeRangeLogin($val['key'], $startTime, $endTime);
} else {

// 不存在,嘗試從數據庫中讀取
$logInfo = $this->_dbHandle->getUserTimeRangeLogin($type, $uid, $val['time'], $startTime, $endTime);
}

if (is_array($logInfo)) {
$hasCount += $logInfo['hasLog']['count'];
$hasList = array_merge($hasList, $logInfo['hasLog']['list']);
$notCount += $logInfo['notLog']['count'];
$notList = array_merge($notList, $logInfo['notLog']['list']);
$successFlg = true;
}
}
}
}

if ($successFlg) {
return array(
'hasLog' => array(
'count' => $hasCount,
'list' => $hasList
),
'notLog' => array(
'count' => $notCount,
'list' => $notList
)
);
}

return array();
}

/**
* 獲取某段時間內有效登錄過的用戶 統一接口
* @param int  $startTime 開始時間戳
* @param int  $endTime  結束時間戳
* @param array $typeArr  用戶類型,為空時獲取全部類型
* @return array 參數錯誤或未查詢到返回array()
* -------------------------------------------------
* 查詢到結果:指定用戶類型
* array(
*   'type1' => array(
*     'count' => n,           // type1 有效登錄總用戶數
*     'list' => array('111', '222' ...) // type1 有效登錄用戶
*   ),
*   'type2' => array(
*     'count' => n,           // type2 有效登錄總用戶數
*     'list' => array('333', '444' ...) // type2 有效登錄用戶
*   )
* )
* -------------------------------------------------
* 查詢到結果:未指定用戶類型,全部用戶,固定鍵 'all'
* array(
*   'all' => array(
*     'count' => n,           // 有效登錄總用戶數
*     'list' => array('111', '222' ...) // 有效登錄用戶
*   )
* )
*/
public function getOrientedTimeRangeLogin($startTime, $endTime, $typeArr = array())
{
if ($this->checkTimeRange($startTime, $endTime)) {

// 判斷是否指定類型
if (is_array($typeArr)  !empty($typeArr)) {

// 指定類型,驗證類型合法性
if ($this->checkTypeArr($typeArr)) {

// 依據類型獲取
return $this->getSpecifyTypeTimeRangeLogin($startTime, $endTime, $typeArr);
}
} else {

// 未指定類型,統一獲取
return $this->getSpecifyAllTimeRangeLogin($startTime, $endTime);
}
}
return array();
}

/**
* 指定類型:獲取某段時間內登錄過的用戶
* @param int  $startTime 開始時間戳
* @param int  $endTime  結束時間戳
* @param array $typeArr  用戶類型
* @return array
*/
private function getSpecifyTypeTimeRangeLogin($startTime, $endTime, $typeArr)
{
$data = array();
$successFlg = false; // 查詢到數據標識

// 指定類型,根據類型單獨獲取,進行整合
foreach ($typeArr as $typeArrVal) {

// 獲取需要查詢的Key
$keyList = $this->_redisHandle->getSpecifyTypeTimeRangeRedisKey($typeArrVal, $startTime, $endTime);
if (!empty($keyList)) {

$data[$typeArrVal]['count'] = 0;    // 該類型下有效登錄用戶數
$data[$typeArrVal]['list'] = array(); // 該類型下有效登錄用戶

foreach ($keyList as $keyListVal) {

// 查詢Kye,驗證Redis中是否存在:此處為單個類型,所以直接看Redis中是否存在該類型Key即可判斷是否存在
// 存在的數據不需要去數據庫中去查看
$standardKeyList = $this->_redisHandle->getKeys($keyListVal['key']);
if (is_array($standardKeyList)  count($standardKeyList) > 0) {

// Redis存在
foreach ($standardKeyList as $standardKeyListVal) {

// 驗證該用戶在此時間段是否登錄過
$redisCheckLogin = $this->_redisHandle->getUserTimeRangeLogin($standardKeyListVal, $startTime, $endTime);
if ($redisCheckLogin['hasLog']['count'] > 0) {

// 同一個用戶只需記錄一次
$uid = $this->_redisHandle->getLoginLogKeyInfo($standardKeyListVal, 'uid');
if (!in_array($uid, $data[$typeArrVal]['list'])) {
$data[$typeArrVal]['count']++;
$data[$typeArrVal]['list'][] = $uid;
}
$successFlg = true;
}
}

} else {

// 不存在,嘗試從數據庫中獲取
$dbResult = $this->_dbHandle->getTimeRangeLoginSuccessUser($keyListVal['time'], $startTime, $endTime, $typeArrVal);
if (!empty($dbResult)) {
foreach ($dbResult as $dbResultVal) {
if (!in_array($dbResultVal, $data[$typeArrVal]['list'])) {
$data[$typeArrVal]['count']++;
$data[$typeArrVal]['list'][] = $dbResultVal;
}
}
$successFlg = true;
}
}
}
}
}

if ($successFlg) { return $data; }
return array();
}

/**
* 全部類型:獲取某段時間內登錄過的用戶
* @param int  $startTime 開始時間戳
* @param int  $endTime  結束時間戳
* @return array
*/
private function getSpecifyAllTimeRangeLogin($startTime, $endTime)
{
$count   = 0;    // 有效登錄用戶數
$list    = array(); // 有效登錄用戶
$successFlg = false;  // 查詢到數據標識

// 未指定類型,直接對所有數據進行檢索
// 獲取需要查詢的Key
$keyList = $this->_redisHandle->getSpecifyAllTimeRangeRedisKey($startTime, $endTime);

if (!empty($keyList)) {
foreach ($keyList as $keyListVal) {

// 查詢Kye
$standardKeyList = $this->_redisHandle->getKeys($keyListVal['key']);

if (is_array($standardKeyList)  count($standardKeyList) > 0) {

// 查詢到Key,直接讀取數據,記錄類型
foreach ($standardKeyList as $standardKeyListVal) {

// 驗證該用戶在此時間段是否登錄過
$redisCheckLogin = $this->_redisHandle->getUserTimeRangeLogin($standardKeyListVal, $startTime, $endTime);
if ($redisCheckLogin['hasLog']['count'] > 0) {

// 同一個用戶只需記錄一次
$uid = $this->_redisHandle->getLoginLogKeyInfo($standardKeyListVal, 'uid');
if (!in_array($uid, $list)) {
$count++;
$list[] = $uid;
}
$successFlg = true;
}
}
}

// 無論Redis中存在不存在都要嘗試從數據庫中獲取一遍數據,來補充Redis獲取的數據,保證檢索數據完整(Redis類型缺失可能導致)
$dbResult = $this->_dbHandle->getTimeRangeLoginSuccessUser($keyListVal['time'], $startTime, $endTime);
if (!empty($dbResult)) {
foreach ($dbResult as $dbResultVal) {
if (!in_array($dbResultVal, $list)) {
$count++;
$list[] = $dbResultVal;
}
}
$successFlg = true;
}
}
}

if ($successFlg) {
return array(
'all' => array(
'count' => $count,
'list' => $list
)
);
}
return array();
}

/**
* 驗證開始結束時間
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return boolean
*/
private function checkTimeRange($startTime, $endTime)
{
return $this->_redisHandle->checkTimeRange($startTime, $endTime);
}

/**
* 批量驗證用戶類型
* @param array $typeArr 用戶類型數組
* @return boolean
*/
private function checkTypeArr($typeArr)
{
$flg = false;
if (is_array($typeArr)  !empty($typeArr)) {
foreach ($typeArr as $val) {
if ($this->_redisHandle->checkType($val)) {
$flg = true;
} else {
$flg = false; break;
}
}
}
return $flg;
}

/**
* 定時任務每周調用一次:從Redis同步登錄日志到數據庫
* @param int  $existsDay 一條記錄在Redis中過期時間,單位:天,必須大于31
* @return string
* 'null':  Redis中無數據
* 'fail':  同步失敗
* 'success':同步成功
*/
public function cronWeeklySync($existsDay)
{

// 驗證生存時間
if ($this->_redisHandle->checkExistsDay($existsDay)) {
$likeKey = 'loginLog_*';
$keyList = $this->_redisHandle->getKeys($likeKey);

if (!empty($keyList)) {
foreach ($keyList as $keyVal) {

if ($this->_redisHandle->checkLoginLogKey($keyVal)) {
$keyTime     = $this->_redisHandle->getLoginLogKeyInfo($keyVal, 'time');
$thisMonth    = date('Y-m');
$beforeMonth   = date('Y-m', strtotime('-1 month'));

// 驗證是否需要進行同步:
// 1. 當前日期 >= 8號,對本月所有記錄進行同步,不對本月之前的記錄進行同步
// 2. 當前日期  8號,對本月所有記錄進行同步,對本月前一個月的記錄進行同步,對本月前一個月之前的所有記錄不進行同步
if (date('j') >= 8) {

// 只同步本月數據
if ($thisMonth == $keyTime) {
$this->redis2db($keyVal);
}
} else {

// 同步本月或本月前一個月數據
if ($thisMonth == $keyTime || $beforeMonth == $keyTime) {
$this->redis2db($keyVal);
}
}

// 驗證是否過期
$existsSecond = $existsDay * 24 * 60 * 60;
if (strtotime($keyTime) + $existsSecond  time()) {

// 過期刪除
$bitMap = $this->_redisHandle->getLoginLogBitMap($keyVal);
Log::INFO('刪除過期數據[' . $keyVal . ']:' . $bitMap);
$this->_redisHandle->delLoginLog($keyVal);
}
}
}
return 'success';
}
return 'null';
}
return 'fail';
}

/**
* 將記錄同步到數據庫
* @param string $key 記錄Key
* @return boolean
*/
private function redis2db($key)
{
if ($this->_redisHandle->checkLoginLogKey($key)  $this->_redisHandle->checkRedisLogExists($key)) {
$time = $this->_redisHandle->getLoginLogKeyInfo($key, 'time');
$data['id']   = Tools::generateId();
$data['user_id'] = $this->_redisHandle->getLoginLogKeyInfo($key, 'uid');
$data['type']  = $this->_redisHandle->getLoginLogKeyInfo($key, 'type');
$data['year']  = date('Y', strtotime($time));
$data['month']  = date('n', strtotime($time));
$data['bit_log'] = $this->_redisHandle->getLoginLogBitMap($key);
return $this->_dbHandle->redis2db($data);
}
return false;
}
}

4.2 LoginLogCommon.class.php

?php

namespace Lib\LoginLog;

use Lib\RedisData;
use Lib\Status;

/**
* 公共方法
* User: dbn
* Date: 2017/10/11
* Time: 13:11
*/
class LoginLogCommon
{
protected $_loginLog;
protected $_redis;

public function __construct(LoginLog $loginLog)
{
$this->_loginLog = $loginLog;
$this->_redis  = RedisData::getRedis();
}

/**
* 驗證用戶類型
* @param string $type 用戶類型
* @return boolean
*/
protected function checkType($type)
{
if (in_array($type, array(
Status::LOGIN_LOG_TYPE_ADMIN,
Status::LOGIN_LOG_TYPE_CARRIER,
Status::LOGIN_LOG_TYPE_DRIVER,
Status::LOGIN_LOG_TYPE_OFFICE,
Status::LOGIN_LOG_TYPE_CLIENT,
))) {
return true;
}
$this->_loginLog->setError('未定義的日志類型:' . $type);
return false;
}

/**
* 驗證唯一標識
* @param string $uid
* @return boolean
*/
protected function checkUid($uid)
{
if (is_numeric($uid)  $uid > 0) {
return true;
}
$this->_loginLog->setError('唯一標識非法:' . $uid);
return false;
}

/**
* 驗證時間戳
* @param string $time
* @return boolean
*/
protected function checkTime($time)
{
if (is_numeric($time)  $time > 0) {
return true;
}
$this->_loginLog->setError('時間戳非法:' . $time);
return false;
}

/**
* 驗證時間是否在當月中
* @param string $time
* @return boolean
*/
protected function checkTimeWhetherThisMonth($time)
{
if ($this->checkTime($time)  $time > strtotime(date('Y-m'))  $time  strtotime(date('Y-m') . '-' . date('t'))) {
return true;
}
$this->_loginLog->setError('時間未在當前月份中:' . $time);
return false;
}

/**
* 驗證時間是否超過當前時間
* @param string $time
* @return boolean
*/
protected function checkTimeWhetherFutureTime($time)
{
if ($this->checkTime($time)  $time = time()) {
return true;
}
return false;
}

/**
* 驗證開始/結束時間
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return boolean
*/
protected function checkTimeRange($startTime, $endTime)
{
if ($this->checkTime($startTime) 
$this->checkTime($endTime) 
$startTime  $endTime 
$startTime  time()
) {
return true;
}
$this->_loginLog->setError('時間范圍非法:' . $startTime . '-' . $endTime);
return false;
}

/**
* 驗證時間是否在指定范圍內
* @param string $time   需要檢查的時間
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return boolean
*/
protected function checkTimeWithinTimeRange($time, $startTime, $endTime)
{
if ($this->checkTime($time) 
$this->checkTimeRange($startTime, $endTime) 
$startTime = $time 
$time = $endTime
) {
return true;
}
$this->_loginLog->setError('請求時間未在時間范圍內:' . $time . '-' . $startTime . '-' . $endTime);
return false;
}

/**
* 驗證Redis日志記錄標準Key
* @param string $key
* @return boolean
*/
protected function checkLoginLogKey($key)
{
$pattern = '/^loginLog_\d{4}-\d{1,2}_\S+_\d+$/';
$result = preg_match($pattern, $key, $match);
if ($result > 0) {
return true;
}
$this->_loginLog->setError('RedisKey非法:' . $key);
return false;
}

/**
* 獲取月份中有多少天
* @param int $time 時間戳
* @return int
*/
protected function getDaysInMonth($time)
{
return date('t', $time);
}

/**
* 對沒有前導零的月份或日設置前導零
* @param int $num 月份或日
* @return string
*/
protected function setDateLeadingZero($num)
{
if (is_numeric($num)  strlen($num) = 2) {
$num = (strlen($num) > 1 ? $num : '0' . $num);
}
return $num;
}

/**
* 驗證過期時間
* @param int   $existsDay 一條記錄在Redis中過期時間,單位:天,必須大于31
* @return boolean
*/
protected function checkExistsDay($existsDay)
{
if (is_numeric($existsDay)  ctype_digit(strval($existsDay))  $existsDay > 31) {
return true;
}
$this->_loginLog->setError('過期時間非法:' . $existsDay);
return false;
}

/**
* 獲取開始日期邊界
* @param int $time   需要判斷的時間戳
* @param int $startTime 起始時間
* @return int
*/
protected function getStartTimeBorder($time, $startTime)
{
$initDay = 1;
if ($this->checkTime($time)  $this->checkTime($startTime) 
date('Y-m', $time) === date('Y-m', $startTime)  false !== date('Y-m', $time)) {
$initDay = date('j', $startTime);
}
return $initDay;
}

/**
* 獲取結束日期邊界
* @param int $time   需要判斷的時間戳
* @param int $endTime  結束時間
* @return int
*/
protected function getEndTimeBorder($time, $endTime)
{
$border = $this->getDaysInMonth($time);
if ($this->checkTime($time)  $this->checkTime($endTime) 
date('Y-m', $time) === date('Y-m', $endTime)  false !== date('Y-m', $time)) {
$border = date('j', $endTime);
}
return $border;
}
}

4.3 LoginLogDBHandle.class.php

?php

namespace Lib\LoginLog;
use Think\Model;

/**
* 數據庫登錄日志處理類
* User: dbn
* Date: 2017/10/11
* Time: 13:12
*/
class LoginLogDBHandle extends LoginLogCommon
{

/**
* 從數據庫中獲取用戶某月記錄在指定時間范圍內的用戶信息
* @param string $type   用戶類型
* @param int   $uid    唯一標識(用戶ID)
* @param int   $time   需要查詢月份時間戳
* @param int   $startTime 開始時間戳
* @param int   $endTime  結束時間戳
* @return array
* array(
*   'hasLog' => array(
*     'count' => n,                 // 有效登錄次數,每天重復登錄算一次
*     'list' => array('2017-10-1', '2017-10-15' ...) // 有效登錄日期
*   ),
*   'notLog' => array(
*     'count' => n,                 // 未登錄次數
*     'list' => array('2017-10-1', '2017-10-15' ...) // 未登錄日期
*   )
* )
*/
public function getUserTimeRangeLogin($type, $uid, $time, $startTime, $endTime)
{
$hasCount = 0;    // 有效登錄次數
$notCount = 0;    // 未登錄次數
$hasList = array(); // 有效登錄日期
$notList = array(); // 未登錄日期

if ($this->checkType($type)  $this->checkUid($uid)  $this->checkTimeWithinTimeRange($time, $startTime, $endTime)) {

$timeYM = date('Y-m', $time);

// 設置開始時間
$initDay = $this->getStartTimeBorder($time, $startTime);

// 設置結束時間
$border = $this->getEndTimeBorder($time, $endTime);

$bitMap = $this->getBitMapFind($type, $uid, date('Y', $time), date('n', $time));
for ($i = $initDay; $i = $border; $i++) {

if (!empty($bitMap)) {
if ($bitMap[$i-1] == '1') {
$hasCount++;
$hasList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
} else {
$notCount++;
$notList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
}
} else {
$notCount++;
$notList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
}
}
}

return array(
'hasLog' => array(
'count' => $hasCount,
'list' => $hasList
),
'notLog' => array(
'count' => $notCount,
'list' => $notList
)
);
}

/**
* 從數據庫獲取用戶某月日志位圖
* @param string $type 用戶類型
* @param int   $uid  唯一標識(用戶ID)
* @param int   $year 年Y
* @param int   $month 月n
* @return string
*/
private function getBitMapFind($type, $uid, $year, $month)
{
$model = D('Home/StatLoginLog');
$map['type']  = array('EQ', $type);
$map['user_id'] = array('EQ', $uid);
$map['year']  = array('EQ', $year);
$map['month']  = array('EQ', $month);

$result = $model->field('bit_log')->where($map)->find();
if (false !== $result  isset($result['bit_log'])  !empty($result['bit_log'])) {
return $result['bit_log'];
}
return '';
}

/**
* 從數據庫中判斷用戶在某一天是否登錄過
* @param string $type 用戶類型
* @param int   $uid  唯一標識(用戶ID)
* @param int   $time 時間戳
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function dateWhetherLogin($type, $uid, $time)
{
if ($this->checkType($type)  $this->checkUid($uid)  $this->checkTime($time)) {

$timeInfo = getdate($time);
$bitMap = $this->getBitMapFind($type, $uid, $timeInfo['year'], $timeInfo['mon']);
if (!empty($bitMap)) {
if ($bitMap[$timeInfo['mday']-1] == '1') {
return true;
}
}
}
return false;
}

/**
* 從數據庫中判斷用戶在某月是否登錄過
* @param string $type 用戶類型
* @param int   $uid  唯一標識(用戶ID)
* @param int   $time 時間戳
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function dateMonthWhetherLogin($type, $uid, $time)
{
if ($this->checkType($type)  $this->checkUid($uid)  $this->checkTime($time)) {

$timeInfo = getdate($time);
$userArr = $this->getMonthLoginSuccessUser($timeInfo['year'], $timeInfo['mon'], $type);
if (!empty($userArr)) {
if (in_array($uid, $userArr)) {
return true;
}
}
}
return false;
}

/**
* 獲取某月所有有效登錄過的用戶ID
* @param int   $year 年Y
* @param int   $month 月n
* @param string $type 用戶類型,為空時獲取全部類型
* @return array
*/
public function getMonthLoginSuccessUser($year, $month, $type = '')
{
$data = array();
if (is_numeric($year)  is_numeric($month)) {
$model = D('Home/StatLoginLog');
$map['year']  = array('EQ', $year);
$map['month']  = array('EQ', $month);
$map['bit_log'] = array('LIKE', '%1%');
if ($type != ''  $this->checkType($type)) {
$map['type']  = array('EQ', $type);
}
$result = $model->field('user_id')->where($map)->select();
if (false !== $result  count($result) > 0) {
foreach ($result as $val) {
if (isset($val['user_id'])) {
$data[] = $val['user_id'];
}
}
}
}
return $data;
}

/**
* 從數據庫中獲取某月所有記錄在指定時間范圍內的用戶ID
* @param int   $time   查詢的時間戳
* @param int   $startTime 開始時間戳
* @param int   $endTime  結束時間戳
* @param string $type 用戶類型,為空時獲取全部類型
* @return array
*/
public function getTimeRangeLoginSuccessUser($time, $startTime, $endTime, $type = '')
{
$data = array();
if ($this->checkTimeWithinTimeRange($time, $startTime, $endTime)) {

$timeInfo = getdate($time);

// 獲取滿足時間條件的記錄
$model = D('Home/StatLoginLog');
$map['year']  = array('EQ', $timeInfo['year']);
$map['month']  = array('EQ', $timeInfo['mon']);
if ($type != ''  $this->checkType($type)) {
$map['type']  = array('EQ', $type);
}

$result = $model->where($map)->select();
if (false !== $result  count($result) > 0) {

// 設置開始時間
$initDay = $this->getStartTimeBorder($time, $startTime);

// 設置結束時間
$border = $this->getEndTimeBorder($time, $endTime);

foreach ($result as $val) {

$bitMap = $val['bit_log'];
for ($i = $initDay; $i = $border; $i++) {

if ($bitMap[$i-1] == '1'  !in_array($val['user_id'], $data)) {
$data[] = $val['user_id'];
}
}
}
}
}
return $data;
}

/**
* 將數據更新到數據庫
* @param array $data 單條記錄的數據
* @return boolean
*/
public function redis2db($data)
{
$model = D('Home/StatLoginLog');

// 驗證記錄是否存在
$map['user_id'] = array('EQ', $data['user_id']);
$map['type']  = array('EQ', $data['type']);
$map['year']  = array('EQ', $data['year']);
$map['month']  = array('EQ', $data['month']);

$count = $model->where($map)->count();
if (false !== $count  $count > 0) {

// 存在記錄進行更新
$saveData['bit_log'] = $data['bit_log'];

if (!$model->create($saveData, Model::MODEL_UPDATE)) {

$this->_loginLog->setError('同步登錄日志-更新記錄,創建數據對象失敗:' . $model->getError());
logger()->error('同步登錄日志-更新記錄,創建數據對象失敗:' . $model->getError());
return false;
} else {

$result = $model->where($map)->save();

if (false !== $result) {
return true;
} else {
$this->_loginLog->setError('同步登錄日志-更新記錄,更新數據失敗:' . json_encode($data));
logger()->error('同步登錄日志-更新記錄,更新數據失敗:' . json_encode($data));
return false;
}
}
} else {

// 不存在記錄插入一條新的記錄
if (!$model->create($data, Model::MODEL_INSERT)) {

$this->_loginLog->setError('同步登錄日志-插入記錄,創建數據對象失敗:' . $model->getError());
logger()->error('同步登錄日志-插入記錄,創建數據對象失敗:' . $model->getError());
return false;
} else {

$result = $model->add();

if (false !== $result) {
return true;
} else {
$this->_loginLog->setError('同步登錄日志-插入記錄,插入數據失敗:' . json_encode($data));
logger()->error('同步登錄日志-插入記錄,插入數據失敗:' . json_encode($data));
return false;
}
}
}
}
}

4.4 LoginLogRedisHandle.class.php

?php

namespace Lib\LoginLog;

/**
* Redis登錄日志處理類
* User: dbn
* Date: 2017/10/11
* Time: 15:53
*/
class LoginLogRedisHandle extends LoginLogCommon
{
/**
* 記錄登錄:每天只記錄一次登錄,只允許設置當月內登錄記錄
* @param string $key 日志記錄Key
* @param int  $time 時間戳
* @return boolean
*/
public function setLogging($key, $time)
{
if ($this->checkLoginLogKey($key)  $this->checkTimeWhetherThisMonth($time)) {

// 判斷用戶當天是否已經登錄過
$whetherLoginResult = $this->dateWhetherLogin($key, $time);
if (!$whetherLoginResult) {

// 當天未登錄,記錄登錄
$this->_redis->setBit($key, date('d', $time), 1);
}
return true;
}
return false;
}

/**
* 從Redis中判斷用戶在某一天是否登錄過
* @param string $key 日志記錄Key
* @param int  $time 時間戳
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function dateWhetherLogin($key, $time)
{
if ($this->checkLoginLogKey($key)  $this->checkTime($time)) {
$result = $this->_redis->getBit($key, date('d', $time));
if ($result === 1) {
return true;
}
}
return false;
}

/**
* 從Redis中判斷用戶在某月是否登錄過
* @param string $key 日志記錄Key
* @return boolean 參數錯誤或未登錄過返回false,登錄過返回true
*/
public function dateMonthWhetherLogin($key)
{
if ($this->checkLoginLogKey($key)) {
$result = $this->_redis->bitCount($key);
if ($result > 0) {
return true;
}
}
return false;
}

/**
* 判斷某月登錄記錄在Redis中是否存在
* @param string $key 日志記錄Key
* @return boolean
*/
public function checkRedisLogExists($key)
{
if ($this->checkLoginLogKey($key)) {
if ($this->_redis->exists($key)) {
return true;
}
}
return false;
}

/**
* 從Redis中獲取用戶某月記錄在指定時間范圍內的用戶信息
* @param string $key    日志記錄Key
* @param int   $startTime 開始時間戳
* @param int   $endTime  結束時間戳
* @return array
* array(
*   'hasLog' => array(
*     'count' => n,                 // 有效登錄次數,每天重復登錄算一次
*     'list' => array('2017-10-1', '2017-10-15' ...) // 有效登錄日期
*   ),
*   'notLog' => array(
*     'count' => n,                 // 未登錄次數
*     'list' => array('2017-10-1', '2017-10-15' ...) // 未登錄日期
*   )
* )
*/
public function getUserTimeRangeLogin($key, $startTime, $endTime)
{
$hasCount = 0;    // 有效登錄次數
$notCount = 0;    // 未登錄次數
$hasList = array(); // 有效登錄日期
$notList = array(); // 未登錄日期

if ($this->checkLoginLogKey($key)  $this->checkTimeRange($startTime, $endTime)  $this->checkRedisLogExists($key)) {

$keyTime = $this->getLoginLogKeyInfo($key, 'time');
$keyTime = strtotime($keyTime);
$timeYM = date('Y-m', $keyTime);

// 設置開始時間
$initDay = $this->getStartTimeBorder($keyTime, $startTime);

// 設置結束時間
$border = $this->getEndTimeBorder($keyTime, $endTime);

for ($i = $initDay; $i = $border; $i++) {
$result = $this->_redis->getBit($key, $i);
if ($result === 1) {
$hasCount++;
$hasList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
} else {
$notCount++;
$notList[] = $timeYM . '-' . $this->setDateLeadingZero($i);
}
}
}

return array(
'hasLog' => array(
'count' => $hasCount,
'list' => $hasList
),
'notLog' => array(
'count' => $notCount,
'list' => $notList
)
);
}

/**
* 面向用戶:獲取時間范圍內可能需要的Key
* @param string $type   用戶類型
* @param int  $uid    唯一標識(用戶ID)
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return array
*/
public function getTimeRangeRedisKey($type, $uid, $startTime, $endTime)
{
$list = array();

if ($this->checkType($type)  $this->checkUid($uid)  $this->checkTimeRange($startTime, $endTime)) {

$data = $this->getSpecifyUserKeyHandle($type, $uid, $startTime);
if (!empty($data)) { $list[] = $data; }

$temYM = strtotime('+1 month', strtotime(date('Y-m', $startTime)));

while ($temYM = $endTime) {
$data = $this->getSpecifyUserKeyHandle($type, $uid, $temYM);
if (!empty($data)) { $list[] = $data; }

$temYM = strtotime('+1 month', $temYM);
}
}
return $list;
}
private function getSpecifyUserKeyHandle($type, $uid, $time)
{
$data = array();
$key = $this->getLoginLogKey($type, $uid, $time);
if ($this->checkLoginLogKey($key)) {
$data = array(
'key' => $key,
'time' => $time
);
}
return $data;
}

/**
* 面向類型:獲取時間范圍內可能需要的Key
* @param string $type   用戶類型
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return array
*/
public function getSpecifyTypeTimeRangeRedisKey($type, $startTime, $endTime)
{
$list = array();

if ($this->checkType($type)  $this->checkTimeRange($startTime, $endTime)) {

$data = $this->getSpecifyTypeKeyHandle($type, $startTime);
if (!empty($data)) { $list[] = $data; }

$temYM = strtotime('+1 month', strtotime(date('Y-m', $startTime)));

while ($temYM = $endTime) {
$data = $this->getSpecifyTypeKeyHandle($type, $temYM);
if (!empty($data)) { $list[] = $data; }

$temYM = strtotime('+1 month', $temYM);
}
}
return $list;
}
private function getSpecifyTypeKeyHandle($type, $time)
{
$data = array();
$temUid = '11111111';

$key = $this->getLoginLogKey($type, $temUid, $time);
if ($this->checkLoginLogKey($key)) {
$arr = explode('_', $key);
$arr[count($arr)-1] = '*';
$key = implode('_', $arr);
$data = array(
'key' => $key,
'time' => $time
);
}
return $data;
}

/**
* 面向全部:獲取時間范圍內可能需要的Key
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return array
*/
public function getSpecifyAllTimeRangeRedisKey($startTime, $endTime)
{
$list = array();

if ($this->checkTimeRange($startTime, $endTime)) {

$data = $this->getSpecifyAllKeyHandle($startTime);
if (!empty($data)) { $list[] = $data; }

$temYM = strtotime('+1 month', strtotime(date('Y-m', $startTime)));

while ($temYM = $endTime) {
$data = $this->getSpecifyAllKeyHandle($temYM);
if (!empty($data)) { $list[] = $data; }

$temYM = strtotime('+1 month', $temYM);
}
}
return $list;
}
private function getSpecifyAllKeyHandle($time)
{
$data = array();
$temUid = '11111111';
$temType = 'office';

$key = $this->getLoginLogKey($temType, $temUid, $time);
if ($this->checkLoginLogKey($key)) {
$arr = explode('_', $key);
array_pop($arr);
$arr[count($arr)-1] = '*';
$key = implode('_', $arr);
$data = array(
'key' => $key,
'time' => $time
);
}
return $data;
}

/**
* 從Redis中查詢滿足條件的Key
* @param string $key 查詢的Key
* @return array
*/
public function getKeys($key)
{
return $this->_redis->keys($key);
}

/**
* 從Redis中刪除記錄
* @param string $key 記錄的Key
* @return boolean
*/
public function delLoginLog($key)
{
return $this->_redis->del($key);
}

/**
* 獲取日志標準Key:前綴_年-月_用戶類型_唯一標識
* @param string $type 用戶類型
* @param int  $uid 唯一標識(用戶ID)
* @param int  $time 時間戳
* @return string
*/
public function getLoginLogKey($type, $uid, $time)
{
if ($this->checkType($type)  $this->checkUid($uid)  $this->checkTime($time)) {
return 'loginLog_' . date('Y-m', $time) . '_' . $type . '_' . $uid;
}
return '';
}

/**
* 獲取日志標準Key上信息
* @param string $key  key
* @param string $field 需要的參數 time,type,uid
* @return mixed 返回對應的值,沒有返回null
*/
public function getLoginLogKeyInfo($key, $field)
{
$param = array();
if ($this->checkLoginLogKey($key)) {
$arr = explode('_', $key);
$param['time'] = $arr[1];
$param['type'] = $arr[2];
$param['uid'] = $arr[3];
}
return $param[$field];
}

/**
* 獲取Key記錄的登錄位圖
* @param string $key key
* @return string
*/
public function getLoginLogBitMap($key)
{
$bitMap = '';
if ($this->checkLoginLogKey($key)) {
$time = $this->getLoginLogKeyInfo($key, 'time');
$maxDay = $this->getDaysInMonth(strtotime($time));
for ($i = 1; $i = $maxDay; $i++) {
$bitMap .= $this->_redis->getBit($key, $i);
}
}
return $bitMap;
}

/**
* 驗證日志標準Key
* @param string $key
* @return boolean
*/
public function checkLoginLogKey($key)
{
return parent::checkLoginLogKey($key);
}

/**
* 驗證開始/結束時間
* @param string $startTime 開始時間
* @param string $endTime  結束時間
* @return boolean
*/
public function checkTimeRange($startTime, $endTime)
{
return parent::checkTimeRange($startTime, $endTime);
}

/**
* 驗證用戶類型
* @param string $type
* @return boolean
*/
public function checkType($type)
{
return parent::checkType($type);
}

/**
* 驗證過期時間
* @param int $existsDay 一條記錄在Redis中過期時間,單位:天,必須大于31
* @return boolean
*/
public function checkExistsDay($existsDay)
{
return parent::checkExistsDay($existsDay);
}
}

5. 參考資料

  https://segmentfault.com/a/1190000008188655

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

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • PHP使用redis位圖bitMap 實現簽到功能
  • Redis精確去重計數方法(咆哮位圖)
  • redis通過位圖法記錄在線用戶的狀態詳解
  • java redis 實現簡單的用戶簽到功能
  • 基于Redis位圖實現用戶簽到功能

標簽:北京 果洛 吉安 大慶 楊凌 朝陽 江蘇 臺州

巨人網絡通訊聲明:本文標題《基于Redis位圖實現系統用戶登錄統計》,本文關鍵詞  基于,Redis,位圖,實現,系統,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《基于Redis位圖實現系統用戶登錄統計》相關的同類信息!
  • 本頁收集關于基于Redis位圖實現系統用戶登錄統計的相關信息資訊供網民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    日韩一区二区三区四区| 最新久久zyz资源站| 亚洲欧美日韩国产另类专区| 国产在线播放一区三区四| 欧美高清dvd| 免费看欧美美女黄的网站| 91麻豆精品国产自产在线观看一区 | 日韩三级在线观看| 日韩国产欧美三级| 欧美电视剧在线看免费| 高清shemale亚洲人妖| 中文字幕综合网| 91黄色免费版| 免费高清在线一区| 国产亚洲综合在线| 91一区二区在线观看| 秋霞电影网一区二区| 久久综合精品国产一区二区三区| 成人做爰69片免费看网站| 亚洲六月丁香色婷婷综合久久| 欧美视频三区在线播放| 国模大尺度一区二区三区| 综合久久久久综合| 日韩三级视频在线观看| gogogo免费视频观看亚洲一| 日本91福利区| 自拍偷自拍亚洲精品播放| 欧美一区二区三区四区五区| 成人精品国产福利| 日韩成人午夜电影| 中文字幕在线观看不卡| 日韩一区二区三区在线观看| 91视视频在线观看入口直接观看www| 性感美女久久精品| 欧美极品少妇xxxxⅹ高跟鞋 | 亚洲国产另类av| 精品久久久久久久久久久久久久久久久| 粗大黑人巨茎大战欧美成人| 婷婷六月综合亚洲| 亚洲九九爱视频| 国产精品美女久久久久aⅴ | 日本网站在线观看一区二区三区| 国产欧美一区二区精品仙草咪| 欧美精品tushy高清| jlzzjlzz亚洲女人18| 国产在线国偷精品产拍免费yy| 一区二区不卡在线视频 午夜欧美不卡在| 欧美tk—视频vk| 欧美一区在线视频| 欧美日本一道本在线视频| 一本高清dvd不卡在线观看| 国产精品99久| 国产一本一道久久香蕉| 蜜桃一区二区三区在线观看| 日日夜夜免费精品视频| 亚洲高清在线视频| 亚洲欧美在线高清| 国产精品激情偷乱一区二区∴| 久久久影视传媒| 久久这里只有精品首页| 精品国产一区二区三区忘忧草 | 99久久伊人精品| 国产成人高清视频| 国产91高潮流白浆在线麻豆 | 国产在线精品免费| 久久99国产精品免费网站| 久久精品国产77777蜜臀| 久久草av在线| 久久精品国产99国产| 国产乱码精品一区二区三区av | 日本不卡高清视频| 五月开心婷婷久久| 日本 国产 欧美色综合| 三级不卡在线观看| 久久99精品一区二区三区| 韩国成人福利片在线播放| 国产成人精品影视| 91视频免费观看| 日本韩国欧美一区| 在线不卡欧美精品一区二区三区| 884aa四虎影成人精品一区| 日韩精品一区二区三区视频| 久久综合九色综合欧美就去吻 | 欧美性大战久久| 日韩一区二区三区高清免费看看| 日韩亚洲欧美中文三级| 久久久不卡影院| 中文字幕一区视频| 亚洲成人福利片| 日韩成人免费电影| 国产精品一区二区久久不卡 | 亚洲三级理论片| 亚洲成人激情综合网| 国产精品一区二区不卡| 色偷偷一区二区三区| 日韩一级二级三级精品视频| 欧美国产一区视频在线观看| 亚洲男人天堂av| 麻豆91小视频| 91麻豆国产福利精品| 91精品国产综合久久小美女| 国产精品丝袜在线| 美女性感视频久久| 色中色一区二区| 久久精品视频一区| 日韩精品一二三区| 99v久久综合狠狠综合久久| 欧美成人r级一区二区三区| 亚洲欧美激情插 | 日本韩国一区二区三区| 欧美mv日韩mv国产网站app| 日韩一区在线看| 黄色精品一二区| 欧美精品在欧美一区二区少妇| 国产精品对白交换视频| 九一九一国产精品| 欧美日韩高清一区| 亚洲人一二三区| 成人免费视频caoporn| 日韩一区二区电影网| 一区二区三区在线影院| 成人av动漫在线| 久久精品一区二区三区不卡牛牛| 日韩成人免费电影| 欧美人狂配大交3d怪物一区| 亚洲另类春色校园小说| 99久久久无码国产精品| 国产精品久久毛片av大全日韩| 另类小说欧美激情| 欧美一区二区视频网站| 亚洲一二三四在线| 在线观看日韩av先锋影音电影院| 中文字幕第一区第二区| 国产精品一级片在线观看| 久久这里都是精品| 国产露脸91国语对白| 国产午夜精品久久久久久免费视| 麻豆成人91精品二区三区| 欧美一区二区视频网站| 青青国产91久久久久久| 欧美日韩成人一区| 亚洲国产成人高清精品| 欧美色欧美亚洲另类二区| 一区二区三区四区不卡在线| 日本高清不卡视频| 亚洲最大成人综合| 欧美裸体bbwbbwbbw| 蜜臀av性久久久久av蜜臀妖精| 91精品国产综合久久香蕉的特点 | 久久亚洲欧美国产精品乐播| 国产综合成人久久大片91| 精品国产伦一区二区三区观看方式 | 亚洲自拍偷拍图区| 欧美视频在线一区二区三区| 天使萌一区二区三区免费观看| 制服丝袜亚洲精品中文字幕| 九色|91porny| 亚洲国产精品高清| 91久久精品一区二区三| 日本视频一区二区| 国产香蕉久久精品综合网| 91色九色蝌蚪| 免费成人你懂的| 国产精品久久久久婷婷| 欧美日韩三级视频| 裸体健美xxxx欧美裸体表演| 久久精品人人做人人综合| 99这里都是精品| 美女在线观看视频一区二区| 国产精品乱码一区二区三区软件| 色猫猫国产区一区二在线视频| 日本91福利区| 亚洲精品网站在线观看| 精品国产麻豆免费人成网站| 一本一本大道香蕉久在线精品 | 中文一区在线播放| 91精品欧美福利在线观看 | 成人午夜伦理影院| 香蕉影视欧美成人| 亚洲欧洲日韩av| 欧美va天堂va视频va在线| 欧美丝袜丝交足nylons图片| 大尺度一区二区| 日韩不卡一区二区| 亚洲卡通欧美制服中文| 国产婷婷色一区二区三区| 91精品国产综合久久婷婷香蕉| 99精品视频一区二区| 极品少妇一区二区三区精品视频| 伊人一区二区三区| 亚洲国产精品二十页| 日韩精品一区二区三区四区 | 在线观看三级视频欧美| 国产精品一区一区| 青娱乐精品在线视频| 亚洲一二三四久久| 亚洲乱码国产乱码精品精小说| 国产日产欧美一区二区视频| 日韩色视频在线观看| 欧美精品丝袜中出|