123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- <?php
- namespace app\common\model;
- use app\common\library\Redis;
- use app\main\helper\ArrayHelper;
- use app\main\service\UserService;
- use think\Log;
- use think\Model;
- use think\Cookie;
- use think\Db;
- class UserRecentlyRead extends Model
- {
- // 表名
- protected $table = 'user_recently_read';
- // 自动写入时间戳字段
- protected $autoWriteTimestamp = 'int';
- // 定义时间戳字段名
- protected $createTime = 'createtime';
- protected $updateTime = 'updatetime';
- // 追加属性
- protected $append = [
- ];
- //分库分表查询
- protected $db;
- //当前链接的user_id分库
- protected $connectUserId = null;
- /**
- * 设置分库链接数据
- * @param $user_id
- * @return $this
- */
- public function setConnect($user_id)
- {
- if ($this->connectUserId != $user_id) {
- $database = get_db_connect($this->table, $user_id);
- $this->setTable($database['table']);
- $this->connect($database);
- $this->connectUserId = $user_id;
- }
- return $this;
- }
- public function getone($user_id, $book_id)
- {
- return $this->setConnect($user_id)->where(['user_id' => $user_id, 'book_id' => $book_id])->find();
- }
- /**
- * @deprecated
- * @param $recentId
- * @param $book_id
- * @param null $user_id
- * @return bool
- */
- public function removeBook($recentId,$book_id,$user_id = null)
- {
- if(!$user_id) {
- $user_id = UserService::instance()->getUserInfo()->id;
- }
- $db_res = $this->setConnect($user_id)->where(['id'=>$recentId])->update(['flag' => 0]);
- if ($db_res) {
- $redis = Redis::instance();
- $key = $this->getURKey($user_id);
- $ubKey = $this->getUBKey($user_id, $book_id);
- $redis->zrem($key, $ubKey);
- $redis->del($ubKey);
- return true;
- }
- return false;
- }
- /**
- * 得到最近阅读记录
- * @param int $updatetime
- * @param int $pageSize
- * @param null $userId
- * @param bool $getBookInfo 是否获取书籍对象信息
- * @return array totalNum用户总阅读记录数量 data查到的阅读记录对象
- * @throws \Exception
- */
- public function getRecentlyRead($updatetime = 0, $pageSize = 10, $userId = null, $getBookInfo = false)
- {
- $data = [];
- if (!$userId) {
- $userId = UserService::instance()->getUserInfo()->id;
- }
- $redis = Redis::instance();
- $urKey = $this->getURKey($userId);
- if ($updatetime) {
- $pageBegin = $updatetime - 1;
- $resUR = $redis->zrevrangebyscore($urKey, $pageBegin, '-inf', array('limit' => array(0, $pageSize)));
- } else {
- $pageBegin = 0;
- $resUR = $redis->zrevrange($urKey, 0, $pageSize - 1);
- }
- if (empty($resUR)) {
- $res = $this->getRecentlyByDb($userId, $pageBegin, $pageSize);
- $result = $res['data'];
- $count = $res['count'];
- } else {
- $count = $redis->zCard($urKey);
- $result = $this->getRecentlyByRedis($userId, $resUR);
- }
- #region 向最近阅读结果中填充书籍信息
- if ($getBookInfo) {
- $bookIds = array_column($result, 'book_id');
- $booksInfo = model('Book')->getBooksInfo($bookIds);
- foreach ($result as &$item) {
- $bookId = $item['book_id'];
- if (isset($booksInfo[$bookId])) {
- $book = $booksInfo[$bookId];
- $item['image'] = $book['image'];
- $item['book_name'] = $book['name'];
- $item['last_chapter_name'] = $book['last_chapter_name'];
- $item['last_chapter_utime'] = $book['last_chapter_utime'];
- $item['state'] = $book['state'];
- $item['free_stime'] = $book['free_stime'];
- $item['free_etime'] = $book['free_etime'];
- $item['first_chapter_id'] = $book['first_chapter_id'];
- }
- }
- }
- #endregion
- $data['totalNum'] = $count;
- $data['data'] = $result;
- return $data;
- }
- /**
- * 从数据库中获取最近阅读记录
- * @param $userId
- * @param $pageBegin
- * @param $pageSize
- * @return array
- */
- private function getRecentlyByDb($userId, $pageBegin, $pageSize)
- {
- $redis = Redis::instance();
- $page = 0;
- $pageSz = 100;
- $recentlyData = [];
- #region 循环从数据库中获取用户阅读记录信息
- while (true) {
- $offset = $page * $pageSz;
- $recentlyPageData = $this->setConnect($userId)->where(['user_id' => $userId, 'flag' => 1])
- ->limit($offset, $pageSz)
- ->order('updatetime', 'desc')->select();
- if (count($recentlyPageData) == 0) {
- break;
- } elseif (count($recentlyPageData) == $pageSz) {
- $recentlyData = array_merge($recentlyData, $recentlyPageData);
- $page++;
- } elseif (count($recentlyPageData) < $pageSz) {
- $recentlyData = array_merge($recentlyData, $recentlyPageData);
- break;
- }
- }
- $count = count($recentlyData);
- #endregion
- #region 阅读记录转成数组
- $aRecentlyData = [];
- array_walk($recentlyData, function ($recentlyDatum) use (&$aRecentlyData) {
- $aRecentlyData[] = is_array($recentlyDatum) ? $recentlyDatum : $recentlyDatum->toArray();
- });
- #endregion
- #region 阅读记录列表插入key为U-R的zset结构的redis缓存中
- $redisKey = $this->getURKey($userId);
- $aUR_Value = [$redisKey];//构造U-R的值列表,U-R为zset类型,0=>reidsKey,1=>score1,2=>value1,3=>score2,4=>value2...
- $aUB_Key = [];//U-B的key的列表,U-B为hash类型。
- array_walk($aRecentlyData, function ($item) use (&$aUR_Value, &$aUB_Key, $userId) {
- $bookId = $item['book_id'];
- $userReadBookKey = $this->getUBKey($userId, $bookId);
- $aUR_Value[] = $item['updatetime'];
- $aUR_Value[] = $userReadBookKey;
- $aUB_Key[] = $userReadBookKey;
- });
- call_user_func_array(array($redis, 'zadd'), $aUR_Value);
- $redis->expire($redisKey, 43200);
- #endregion
- #region 阅读记录详细信息写入key为U-B的hash结构的redis缓存中
- $getUBRedisIndex = Redis::splitKeysByRule($aUB_Key);
- array_walk($getUBRedisIndex, function ($v, $k) use ($aRecentlyData) {
- $redis = Redis::getRedisConnect($k);
- $pipe = $redis->multi(\Redis::PIPELINE);
- array_walk($v, function ($UB_Key) use ($aRecentlyData, &$pipe) {
- $aKeys = explode(':', $UB_Key);
- $bookId = $aKeys[2];
- $recentItem = ArrayHelper::array_column_search($aRecentlyData, 'book_id', $bookId);
- $pipe->hmset($UB_Key, $recentItem);
- $pipe->expire($UB_Key, 4320);
- });
- $pipe->exec();
- });
- #endregion
- #region 按updatetime输出前$pageSize条数据
- $result = [];
- foreach ($aRecentlyData as $recentlyItem) {
- if (count($result) >= $pageSize) {
- break;
- }
- if ($pageBegin == 0) {
- $result[] = $recentlyItem;
- } elseif ($recentlyItem['updatetime'] < $pageBegin) {
- $result[] = $recentlyItem;
- }
- }
- #endregion
- $r = ['data' => $result, 'count' => $count];
- return $r;
- }
- /**
- * 从redis分页读取最近阅读记录
- * @param $userId
- * @param $resUR
- * @return array|null
- * @throws \Exception
- */
- private function getRecentlyByRedis($userId, $resUR)
- {
- $redis = Redis::instance();
- #region 获取U-R中获取最近阅读书籍列表
- $returnArr = [];
- $bookIdsFromUR = [];
- array_walk($resUR, function ($UB_key) use (&$bookIdsFromUR) {
- $aKeys = explode(':', $UB_key);
- $bookIdsFromUR[] = $aKeys[2];
- });
- #endregion
- #region 从redis中获取U-B信息
- $getUBKeyRedisIndex = Redis::splitKeysByRule($resUR);
- array_walk($getUBKeyRedisIndex, function ($v, $k) use (&$returnArr) {
- $redis = Redis::getRedisConnect($k);
- $pipe = $redis->multi(\Redis::PIPELINE);
- array_walk($v, function ($UB_Key) use (&$pipe) {
- $pipe->hgetall($UB_Key);
- });
- $aRecently = $pipe->exec();
- $aRecently = array_filter($aRecently);
- $returnArr = array_merge($returnArr, $aRecently);
- });
- #endregion
- $bookIdsFromUB = array_column($returnArr, 'book_id');
- $diffBookIds = array_diff($bookIdsFromUR, $bookIdsFromUB);#在U-R中存在,但是在U-B中不存在的书籍id
- if (!empty($diffBookIds)) {
- #region 从数据库中获取当前用户不在U-B中的书籍的阅读记录
- $recentListFromDb = $this->setConnect($userId)->where(['user_id' => $userId, 'flag' => 1])
- ->whereIn('book_id', $diffBookIds)->select();
- $aRecentFromDb = [];
- $aUBKeyWrite = [];//从数据库中获得的阅读记录,需要写入redis,rediskey
- array_walk($recentListFromDb, function ($item) use (&$aRecentFromDb, &$aUBKeyWrite, $userId) {
- $recentItem=$item->toArray();
- $aRecentFromDb[] = $recentItem;
- $aUBKeyWrite[] = $this->getUBKey($userId, $recentItem['book_id']);
- });
- #endregion
- #region 分片写入redis
- $getUBKeyWriteRedisIndex = Redis::splitKeysByRule($aUBKeyWrite);
- array_walk($getUBKeyWriteRedisIndex, function ($v, $k) use ($aRecentFromDb) {
- $redis = Redis::getRedisConnect($k);
- $pipe = $redis->multi(\Redis::PIPELINE);
- array_walk($v, function ($UB_key) use (&$pipe, $aRecentFromDb) {
- $aKeys = explode(':', $UB_key);
- $bookId = $aKeys[2];
- $recentItem = ArrayHelper::array_column_search($aRecentFromDb, 'book_id', $bookId);
- $pipe->hmset($UB_key, $recentItem);
- $pipe->expire($UB_key, 43200);
- });
- $pipe->exec();
- });
- #endregion
- $returnArr = array_merge($returnArr, $aRecentFromDb);
- }
- #region U-R中存在的记录,但是在U-B中不存在,需要在U-R中删除
- $UB_BookIds = array_column($returnArr, 'book_id');
- $removeBookIds = array_diff($bookIdsFromUR, $UB_BookIds);
- if (!empty($removeBookIds)) {
- $removeBookKeys = [];
- foreach ($removeBookIds as $removeBookId) {
- $removeBookKeys[] = $this->getUBKey($userId, $removeBookId);
- }
- array_unshift($removeBookKeys, $this->getURKey($userId));
- call_user_func_array([$redis, 'zrem'], $removeBookKeys);
- }
- #endregion
- //结果集使用updatetime逆序排序
- $sortArr = array_column($returnArr, 'updatetime');
- array_multisort($sortArr, SORT_DESC, $returnArr);
- return $returnArr;
- }
- public function getUBKey($userId, $bookId)
- {
- return "U-B:$userId:$bookId";
- }
- public function getURKey($userId)
- {
- return "U-R:$userId";
- }
- public function getUSKey($userId)
- {
- return "U-S:" . $userId;
- }
- public function getShelfRecentlyByDB($userId, $pageBegin, $pageSize)
- {
- $redis = Redis::instance();
- $page = 0;
- $pageSz = 100;
- $recentlyData = [];
- #region 循环从数据库中获取用户阅读记录信息
- while (true) {
- $offset = $page * $pageSz;
- $recentlyPageData = $this->setConnect($userId)->where([
- 'user_id' => $userId,
- 'book_shelf_add' => 1,
- 'book_shelf_flag' => 1
- ])
- ->limit($offset, $pageSz)
- ->order('updatetime', 'desc')->select();
- if (count($recentlyPageData) == 0) {
- break;
- } elseif (count($recentlyPageData) == $pageSz) {
- $recentlyData = array_merge($recentlyData, $recentlyPageData);
- $page++;
- } elseif (count($recentlyPageData) < $pageSz) {
- $recentlyData = array_merge($recentlyData, $recentlyPageData);
- break;
- }
- }
- #endregion
- #region 阅读记录转成数组
- $aRecentlyData = [];
- array_walk($recentlyData, function ($recentlyDatum) use (&$aRecentlyData) {
- $aRecentlyData[] = is_array($recentlyDatum) ? $recentlyDatum : $recentlyDatum->toArray();
- });
- #endregion
- #region 阅读记录列表插入key为U-S的zset结构的redis缓存中
- $redisKey = $this->getUSKey($userId);
- $aUS_Value = [$redisKey];//构造U-S的值列表,U-S为zset类型,0=>reidsKey,1=>score1,2=>value1,3=>score2,4=>value2...
- $aUB_Key = [];//U-B的key的列表,U-B为hash类型。
- array_walk($aRecentlyData, function ($item) use (&$aUS_Value, &$aUB_Key, $userId) {
- $bookId = $item['book_id'];
- $userReadBookKey = $this->getUBKey($userId, $bookId);
- $aUS_Value[] = $item['updatetime'];
- $aUS_Value[] = $userReadBookKey;
- $aUB_Key[] = $userReadBookKey;
- });
- call_user_func_array(array($redis, 'zadd'), $aUS_Value);
- $redis->expire($redisKey, 43200);
- #endregion
- #region 阅读记录详细信息写入key为U-B的hash结构的redis缓存中
- $getUBRedisIndex = Redis::splitKeysByRule($aUB_Key);
- array_walk($getUBRedisIndex, function ($v, $k) use ($aRecentlyData) {
- $redis = Redis::getRedisConnect($k);
- $pipe = $redis->multi(\Redis::PIPELINE);
- array_walk($v, function ($UB_Key) use ($aRecentlyData, &$pipe) {
- $aKeys = explode(':', $UB_Key);
- $bookId = $aKeys[2];
- $recentItem = ArrayHelper::array_column_search($aRecentlyData, 'book_id', $bookId);
- $pipe->hmset($UB_Key, $recentItem);
- $pipe->expire($UB_Key, 4320);
- });
- $pipe->exec();
- });
- #endregion
- #region 按updatetime输出前$pageSize条数据
- $result = [];
- foreach ($aRecentlyData as $recentlyItem) {
- if (count($result) >= $pageSize) {
- break;
- }
- if ($pageBegin == 0) {
- $result[] = $recentlyItem;
- } elseif ($recentlyItem['updatetime'] < $pageBegin) {
- $result[] = $recentlyItem;
- }
- }
- #endregion
- return $result;
- }
- public function getShelfRecentlyByRedis($userId, $resUS)
- {
- $redis = Redis::instance();
- #region 获取U-S中获取最近阅读书籍列表
- $returnArr = [];
- $bookIdsFromUS = [];
- array_walk($resUS, function ($UB_key) use (&$bookIdsFromUS) {
- $aKeys = explode(':', $UB_key);
- $bookIdsFromUS[] = $aKeys[2];
- });
- #endregion
- #region 从redis中获取U-B信息
- $getUBKeyRedisIndex = Redis::splitKeysByRule($resUS);
- array_walk($getUBKeyRedisIndex, function ($v, $k) use (&$returnArr) {
- $redis = Redis::getRedisConnect($k);
- $pipe = $redis->multi(\Redis::PIPELINE);
- array_walk($v, function ($UB_Key) use (&$pipe) {
- $pipe->hgetall($UB_Key);
- });
- $aRecently = $pipe->exec();
- $aRecently = array_filter($aRecently);
- $returnArr = array_merge($returnArr, $aRecently);
- });
- #endregion
- $bookIdsFromUB = array_column($returnArr, 'book_id');
- $diffBookIds = array_diff($bookIdsFromUS, $bookIdsFromUB);#在U-S中存在,但是在U-B中不存在的书籍id
- if (!empty($diffBookIds)) {
- #region 从数据库中获取当前用户不在U-B中的书籍的阅读记录
- $recentListFromDb = $this->setConnect($userId)->where([
- 'user_id' => $userId,
- 'book_shelf_add' => 1,
- 'book_shelf_flag' => 1
- ])->whereIn('book_id', $diffBookIds)->select();
- $aRecentFromDb = [];
- $aUBKeyWrite = [];//从数据库中获得的阅读记录,需要写入redis,rediskey
- array_walk($recentListFromDb, function ($item) use (&$aRecentFromDb, &$aUBKeyWrite, $userId) {
- $recentItem = $item->toArray();
- $aRecentFromDb[] = $recentItem;
- $aUBKeyWrite[] = $this->getUBKey($userId, $recentItem['book_id']);
- });
- #endregion
- #region 分片写入redis
- $getUBKeyWriteRedisIndex = Redis::splitKeysByRule($aUBKeyWrite);
- array_walk($getUBKeyWriteRedisIndex, function ($v, $k) use ($aRecentFromDb) {
- $redis = Redis::getRedisConnect($k);
- $pipe = $redis->multi(\Redis::PIPELINE);
- array_walk($v, function ($UB_key) use (&$pipe, $aRecentFromDb) {
- $aKeys = explode(':', $UB_key);
- $bookId = $aKeys[2];
- $recentItem = ArrayHelper::array_column_search($aRecentFromDb, 'book_id', $bookId);
- $pipe->hmset($UB_key, $recentItem);
- $pipe->expire($UB_key, 43200);
- });
- $pipe->exec();
- });
- #endregion
- $returnArr = array_merge($returnArr, $aRecentFromDb);
- }
- #region U-R中存在的记录,但是在U-B中不存在,需要在U-R中删除
- $UB_BookIds = array_column($returnArr, 'book_id');
- $removeBookIds = array_diff($bookIdsFromUS, $UB_BookIds);
- if (!empty($removeBookIds)) {
- $removeBookKeys = [];
- foreach ($removeBookIds as $removeBookId) {
- $removeBookKeys[] = $this->getUBKey($userId, $removeBookId);
- }
- array_unshift($removeBookKeys, $this->getURKey($userId));
- call_user_func_array([$redis, 'zrem'], $removeBookKeys);
- }
- #endregion
- //结果集使用updatetime逆序排序
- $sortArr = array_column($returnArr, 'updatetime');
- array_multisort($sortArr, SORT_DESC, $returnArr);
- return $returnArr;
- }
- /*
- * 阅读记录记入cookie 只记录5条数据
- */
- public function recentCookie($book_id, $chapterinfo)
- {
- $read_log = Cookie::get('read') ? Cookie::get('read') : [];
- unset($read_log[$book_id]);
- if (count($read_log) >= 5) {
- $first = array_keys($read_log)[0];
- unset($read_log[$first]);
- }
- $wait['book_id'] = $book_id;
- $wait['chapter_id'] = $chapterinfo['id'];
- $wait['chapter_name'] = $chapterinfo['name'];
- $wait['updatetime'] = time();
- $read_log[$book_id] = $wait;
- Cookie::set('read', $read_log);
- }
- }
|