查看阅读记录>>", "2、精准找书>>", "3、去首页看看>>", ]); return $msg; } /** * @param $message * @return bool|mixed * @throws \Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function getWeinxinSearchBooks($message,$is_water, $admin_id) { $search = $message->Content; $openid = $message->FromUserName; $return = false; $type = 0; //0 精准匹配 1 推荐 if (trim($search)) { $search = addslashes($search); $searchKey = 'SEB:' . $search; $issetBookSearch = true; if (Redis::instance()->exists($searchKey)) { $issetBookSearch = false; $redData = Redis::instance()->get($searchKey); $booksData = json_decode($redData, true); $books = $booksData['books']; $total = $booksData['total']; LogService::info('redis'.json_encode($books)); } else { $where = []; $where['state'] = 1; $where['name|keywords'] = ['like', '%'.addslashes($search).'%']; if ($is_water) { $where['classify_white_list'] = 1; } //模糊查询的排序-- 优先推送全匹配的书籍,所以只取一本即可 wdd 191028 $order = "(case when name = '".$search."' then 0 when name like '".$search."%' then 1 when name like '%".$search."' then 2 when name like '%".$search."%' then 3 else 4 end) asc"; // $total = BookService::instance()->getBookModel()->where($where)->count(); $books = BookService::instance()->getBookModel()->where($where)->page(0, 1)->order($order)->select(); } if ( !empty($books) ) { //判断来源是redis里的还是数据库 if ($issetBookSearch) { $books = array_pop($books); LogService::info('数据库'.json_encode($books)); $saveReData = []; $saveReData['books'] = $books; $saveReData['total'] = 1; Redis::instance()->setex($searchKey, 86400, json_encode($saveReData)); } $return[0] = $books; } else { //没有搜到书 从关键字回复书库取 $type = 1; $where = []; $where['book.state'] = ['eq','1']; $where['gzhreply_recommand.status'] = ['eq','normal']; if ($is_water) { $where['classify_white_list'] = 1; } if ($uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($admin_id, $openid)) { $soruceReadLog = model('UserRecentlyRead')->setConnect($uid)->getRecentlyRead(0,10,$uid); if ($soruceReadLog['totalNum'] > 0) { foreach ($soruceReadLog['data'] as $val) { $bookIds[] = $val['book_id']; } $bookIdsToStr = implode(',', $bookIds); $where['gzhreply_recommand.book_id'] = ['not in', $bookIdsToStr]; } $userInfo = model('User')->getUserInfo($uid); if(empty($userInfo['sex'])){ $where['gzhreply_recommand.sex'] = ['eq','1']; }else{ $where['gzhreply_recommand.sex'] = ['eq',$userInfo['sex']]; } $totalNum = model('GzhreplyRecommand')->join('book', 'book.id = gzhreply_recommand.book_id')->where($where)->count(); $randNum = $totalNum > 3 ? $totalNum-3 : 0; if($randNum>0){ $randNum = mt_rand(0,$randNum); } $result = model('GzhreplyRecommand')->join('book', 'book.id = gzhreply_recommand.book_id')->field('book.*')->where($where)->limit($randNum,3)->select(); if (!empty($result)) { $return = $result; } } } } return [$type, $return]; } /** * 合并游客阅读记录 @todo 已废弃 * @throws \Exception */ public function mergeGuestReadingRecord() { if (UserService::instance()->isLogin() && Cookie::has('read')) { $readTmp = Cookie::get('read'); $userId = UserService::instance()->getUserInfo()->id; foreach ($readTmp as $key => $val) { $book = BookService::instance()->getBookModel()->bookinfo($key); $bookInfo = []; $bookInfo['image'] = $book['image']; $bookInfo['book_name'] = $book['name']; $bookInfo['last_chapter_name'] = $book['last_chapter_name']; $bookInfo['last_chapter_utime'] = $book['last_chapter_utime']; $bookInfo['state'] = $book['state']; $insertData = []; $insertData['user_id'] = $userId; $insertData['book_id'] = $val['book_id']; $insertData['chapter_id'] = $val['chapter_id']; $insertData['chapter_name'] = $val['chapter_name']; $insertData['createtime'] = time(); $insertData['updatetime'] = $val['updatetime']; UserService::instance()->getUserRecentlyReadModel()->setConnect($userId)->insert($insertData); $id = UserService::instance()->getUserRecentlyReadModel()->setConnect($userId)->getLastInsID(); $insertData['id'] = $id; $insertArr = array_merge($insertData, $bookInfo); $key = 'U-R:' . $userId; $hkey = 'U-B:' . $userId . ':' . $val['book_id']; Redis::instance()->hmset($hkey, $insertArr); Redis::instance()->zrem($key, $hkey); Redis::instance()->zadd($key, $insertArr['updatetime'], $hkey); Redis::instance()->expire($key, 43200); Redis::instance()->expire($hkey, 43200); } Cookie::delete('read'); } } /** * 获取书籍与当前章节信息 * @param $book_id * @param $chapter_id * @param $sid * @return \app\main\model\object\ReturnObject */ public function getBookChapterInfo($book_id, $chapter_id, $sid = '', $isWater=false) { try{ //参数错误 if (!$book_id) { return $this->setCode(ErrorCodeConstants::REDIRECT)->setData('/')->getReturn(); } $bookInfoRedirect = $this->getBookIndexPage($book_id)->data; //书籍不存在 $bookinfo = BookService::instance()->getBookModel()->BookInfo($book_id); if ($isWater && (!isset($bookinfo['classify_white_list']) || $bookinfo['classify_white_list'] !=1)){//清水逻辑 按照下架处理 return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([ 'tpl' => 'public/forbiddenread', 'bind' => [ 'book' => $bookinfo, ], ])->getReturn(); } if (!$bookinfo) { return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData(['tpl'=>'public/nobook'])->getReturn(); } if($bookinfo['recommand_book_id']){ $recmooand = BookService::instance()->getBookModel()->BookInfo($bookinfo['recommand_book_id']); if ($recmooand) { $bookinfo['recommand_book_image'] =$recmooand['image']; $bookinfo['recommand_book_name'] =$recmooand['name']; }else{ $bookinfo['recommand_book_image'] = ''; $bookinfo['recommand_book_name'] = ''; } } //书籍已下架 if ($bookinfo['state'] == BookConstants::BOOK_STATE_OFF_SALE) { return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([ 'tpl' => 'public/forbiddenread', 'bind' => [ 'book' => $bookinfo, ], ])->getReturn(); } if (!$chapter_id) { if (!$chapter_id && UserService::instance()->isLogin()) { $user_id = UserService::instance()->getUserInfo()->id; $readlog = $this->getUserRecentlyRead()->getone($user_id, $book_id); if ($readlog) { $chapter_id = $readlog['chapter_id']; } } if (!$chapter_id) { if ($sid) { $chapter_id = $sid; } else { $chapter_id = $bookinfo['first_chapter_id']; } } } //章节参数缺失 if (!$chapter_id) { $this->setCode(ErrorCodeConstants::REDIRECT)->setData($bookInfoRedirect)->getReturn(); } $chapter_data = $this->getChapterInfo($book_id, $chapter_id); //章节异常 if ($chapter_data->code == ErrorCodeConstants::EXCEPTION) { return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($bookInfoRedirect)->getReturn(); } //章节缺失 if ($chapter_data->code == ErrorCodeConstants::RESULT_EMPTY) { return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($bookInfoRedirect)->getReturn(); } $chapter = $chapter_data->data; if(is_array($chapter['content'])){ $content = array_map(function($item){ return '

'.$item.'

'; },$chapter['content']); $chapter['content'] = implode('', $content); } //空内容,无下一章 if (!strip_tags($chapter['content']) && !isset($chapter['next_id'])) { return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([ 'tpl' => 'public/nobook', 'bind' => [ 'book' => $bookinfo, ], ])->getReturn(); } //空内容,有下一章 if (!strip_tags($chapter['content']) && isset($chapter['next_id'])) { return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([ 'tpl' => 'public/nocontent', 'bind' => [ 'book' => $bookinfo, 'chapter' => $chapter, 'next_link' => '/index/book/chapter?book_id=' . $book_id . '&chapter_id=' . $chapter['next_id'], ], ])->getReturn(); } $preid = 0; if (empty($chapter['pre_id'])) { $pre_link = "javascript:consoleMain('已经是第一章了');"; } else { $pre_link = '/index/book/chapter?book_id=' . $book_id . '&chapter_id=' . $chapter['pre_id']; $preid = $chapter['pre_id']; } if ($chapter['next_id']) { $next_link = '/index/book/chapter?book_id=' . $book_id . '&chapter_id=' . $chapter['next_id']; } else { if ($bookinfo['is_finish'] == BookConstants::BOOK_IS_FINISH_YES) { $next_link = '/index/book/statuspage/code/2/book_id/' . $book_id; } else { $next_link = '/index/book/statuspage/code/1/book_id/' . $book_id; } } $data = [ 'chapter' => $chapter, 'book' => $bookinfo, 'pre_link' => $pre_link, 'next_link' => $next_link, 'pre_id' => $preid ]; return $this->setData($data)->getReturn(); }catch (\Exception $e){ return $this->getExceptionReturn($e); } } /** * 获取章节信息 * * @param int $book_id 书籍ID * @param int $page_no 第几页 * @param int $limit 单页条数 * @return \app\main\model\object\ReturnObject|array */ public function getChapterList($book_id, $page_no = 1, $limit = 20) { if ((bool)Config::get('redis.change') == CacheConstants::BOOK_CHAPTER_CACHE_CHANGED_YES) { $cacheKey = CacheConstants::getBookChapterCacheKeyNew($book_id); $code = $this->getConnectCode($cacheKey); $redis = Redis::instanceBookChange($code); $list = $redis->zRange($cacheKey, 0, -1); } else { $redis = Redis::instanceBook(); $cacheKey = CacheConstants::getBookChapterCacheKey($book_id); $list = $redis->lRange($cacheKey, 0, -1); } $all = []; //全部章节 $data = [ 'data' => [], 'pageNo' => $page_no, 'limit' => $limit, 'totalNum' => 0, 'totalPage' => 0, ]; foreach ($list as $key => $value) { if ((bool)Config::get('redis.change') == CacheConstants::BOOK_CHAPTER_CACHE_CHANGED_YES) { $json = json_decode($value, true); } else { $json = json_decode(gzdecode($value), true); } $all[] = [ 'id' => $json[4] ?? $json['chapterId'], 'idx' => count($all) + 1, 'name' => $json[5] ?? $json['chapterName'], ]; } if ($limit > 0) { $data['data'] = array_slice($all, ($page_no - 1) * $limit, $limit); } else { //获取全部 $data['data'] = $all; $data['limit'] = count($all); } if (!$data['data']) { LogService::notice('获取章节列表失败!书籍ID:' . $book_id . ' 分页:' . $page_no . ' 条数:' . $limit); return $this->setCode(ErrorCodeConstants::RESULT_EMPTY)->getReturn(); } $data['totalNum'] = count($all); $data['totalPage'] = ceil(count($all) / $limit); return $this->setData($data)->getReturn(); } /** * 获取章节详情 * * @param int $book_id 书籍ID * @param int $chapter_id 章节ID * @return array | ReturnObject */ public function getChapterInfo($book_id, $chapter_id, $user_id = false) { try{ $chapterList = self::getChapterList($book_id, 1, -1); if ($chapterList->code != ErrorCodeConstants::SUCCESS) { LogService::notice('获取章节列表失败!书籍ID:' . $book_id); return $this->setCode(ErrorCodeConstants::RESULT_EMPTY)->getReturn(); } $data = []; $allChapterIds = array_column($chapterList->data['data'], 'id'); //如果章节不存在 if (!in_array($chapter_id, $allChapterIds)) { $id = 0; foreach ($allChapterIds as $id) { if ($id >= $chapter_id) { break; } } $chapter_id = $id; } foreach ($chapterList->data['data'] as $key => $chapter) { if ($chapter['id'] >= $chapter_id) { $data['name'] = $chapter['name']; $data['idx'] = $chapter['idx']; $data['id'] = $chapter['id']; if ($key == 0) { $data['pre_id'] = ''; } else { $data['pre_id'] = $chapterList->data['data'][$key - 1]['id']; } if (isset($chapterList->data['data'][$key + 1])) { $data['next_id'] = $chapterList->data['data'][$key + 1]['id']; } else { $data['next_id'] = ''; } $chapter_id = $chapter['id']; break; } } $redis = Redis::instance(); $cacheKey = CacheConstants::getBookSingleChapterCacheKey($chapter_id); $aChapter = []; if ($redis->exists($cacheKey)) { if(json_decode($redis->get($cacheKey), true)){ $aChapter = json_decode($redis->get($cacheKey), true); }else{ $aChapter = $redis->get($cacheKey); } } else { $chapter_result = $this->getChapter($book_id, $chapter_id)->data; $data['from'] = $chapter_result['from']; if (array_key_exists('name', $chapter_result)) { $data['name'] = $chapter_result['name']; } if ($content = $chapter_result['content']) { $content = str_replace("\r", '', $content); $arr = explode("\n", $content); foreach ($arr as $value) { $value = ltrim($value); $value = mb_ereg_replace('^( | )+','', $value); $value = mb_convert_encoding($value, "UTF-8", "UTF-8"); //强制转换一次UTF-8编码 if ($value) { $aChapter[] = $value; } } } else { LogService::error('获取章节内容失败!ID:' . $book_id . ' 章节ID:' . $chapter_id); } if ($aChapter) { $keyNum = 'BCN:' . $chapter_id . ':' . date('i'); if ($redis->incr($keyNum) > 10) { $redis->setex($cacheKey, 3600, json_encode($aChapter, true)); } $redis->expire($keyNum, 60); } } $data['content'] = $aChapter; return $this->setData($data)->getReturn(); }catch (\Exception $e){ LogService::exception($e); return $this->setCode(ErrorCodeConstants::EXCEPTION)->getReturn(); } } public function getChapter($book_id, $chapter_id) { $data = ['content'=>'','from'=>'cache']; $content = $this->getChapterEditedModel()->getChapterFromDb($chapter_id); if ($content) { $data = [ 'content' => $content['content'], 'name' => $content['name'], 'from' => 'db', ]; } else { $path = Config::get('site.chapter_path'); if (empty($path)) { $path = 'cppartner'; } $file = $this->getChapterContentPath($path, $book_id, $chapter_id); LogService::info('章节文件路径:' . $file); if (is_file($file)) { $content = file_get_contents($file); $data['content'] = $content; } } return $this->setData($data)->getReturn(); } /** * 获取书籍章节路径 * @param $path * @param $book_id * @param $chapter_id * @return string */ public function getChapterContentPath($path, $book_id, $chapter_id) { return ROOT_PATH . "public/assets/" . $path . self::getPath($book_id) . "{$chapter_id}.txt"; } /** * 获取书籍缓存连接参数 * @param $val * @return int */ public function getConnectCode($val) { $bKey = md5($val, true); $rv = (ord($bKey[3]) & 0xFF) << 24 | (ord($bKey[2]) & 0xFF) << 16 | (ord($bKey[1]) & 0xFF) << 8 | ord($bKey[0]) & 0xFF; return $rv & 0xffffffff; } /** * 根据书籍id获取存储路径 * * @param $book_id * @return string */ protected function getPath($book_id) { $path = '/' . substr($book_id, 0, 1) . 'x' . substr($book_id, 1, 1) . '/' . substr($book_id, 0, 2) . 'x' . substr($book_id, 2, 1) . '/' . substr($book_id, 0, 3) . 'x' . substr($book_id, 3, 1) . '/' . $book_id . '/'; return $path; } /** * @param int $book_id * @param int $referral_id * @return ReturnObject */ public function getGuidChapterIdx($book_id = 0, $referral_id = 0) { try { //链接导粉 if ($referral_id) { //检查内推外派,内推不导粉 $ref_info = UrlService::instance()->getReferralModel()->getone($referral_id,false); if(isset($ref_info['push']) && $ref_info['push']){ return $this->setData(0)->getReturn(); } //推广链接导粉 $idx = UrlService::instance()->getReferralModel()->getone($referral_id); if ($idx) { return $this->setData($idx)->getReturn(); } } $runTime = UserService::instance()->getRunTimeObject(); $admin_id = $runTime->adminId; if (empty($admin_id)) { //网站全局导粉章节数 $web_guide = config('site.book_guide_chapter_idx'); if ($web_guide) { return $this->setData($web_guide)->getReturn(); } else { return $this->setData(0)->getReturn(); } } $df_config = $runTime->adminConfig; $adminIds = []; if ($runTime->agentId) { $adminIds[] = $runTime->agentId; } $adminIds[] = $runTime->channelId; foreach ($adminIds as $adminId) { $guide = 0; //guide表导粉章节数 代理商导粉 $key = 'GUIDE:' . $adminId; if (Redis::instance()->exists($key)) { $guide =(int)Redis::instance()->hget($key, $book_id); } else { $db_data = collection($this->getGuideModel()->field('book_id,chapter_idx')->where('admin_id=' . $adminId)->select())->toArray(); if (empty($db_data)) { Redis::instance()->hset($key, 1, 1); } else { Redis::instance()->hdel($key, 1); $db_data_temp = []; foreach ($db_data as $value) { $db_data_temp[$value['book_id']] = $value['chapter_idx']; if ($value['book_id'] == $book_id) { $guide = $value['chapter_idx']; } } Redis::instance()->hMSet($key,$db_data_temp); unset($db_data_temp); unset($db_data); } Redis::instance()->expire($key, 86400); } if ($guide) { return $this->setData($guide)->getReturn(); } } //渠道商全局导粉章节数 if ($df_config['book_guide_chapter_idx']) { return $this->setData($df_config['book_guide_chapter_idx'])->getReturn(); } //网站全局导粉章节数 $web_guide = config('site.book_guide_chapter_idx'); if ($web_guide) { return $this->setData($web_guide)->getReturn(); } return $this->setData(0)->getReturn(); } catch (\Exception $e) { LogService::exception($e); return $this->setData(0)->getReturn(); } } /** * 随机获取导粉二维码 * @param $admin_id * @return ReturnObject */ public function getRandomGuideQrCode($admin_id) { $data = ''; try { $redis = Redis::instance(); $key = 'GW:' . $admin_id; if ($redis->exists($key)) { $gwRes = $redis->srandmember($key); if ($gwRes === '') { $data = ''; } else { $data = json_decode($gwRes, true); } } else { $result = $this->getGuideWxModel()->where(['admin_id' => $admin_id, 'state' => '1'])->select(); if ($result) { foreach ($result as $val) { $redis->sadd($key, json_encode($val, JSON_UNESCAPED_UNICODE)); } $redis->expire($key, 86400 * 2); $gwRes = $redis->srandmember($key); $data = json_decode($gwRes, true); } else { $data = ''; $redis->sadd($key, ''); $redis->expire($key, 86400 * 2); } } } catch (\Exception $e) { LogService::exception($e); } return $this->setData($data)->getReturn(); } /** * 设置最近阅读 * @param $chapter_name 章节名称 * @param $chapter_id 章节id * @param $bookId 书籍id * @param $chapter_idx 章节索引 * @param null $userId 用户id * @return ReturnObject */ public function setRecentlyRead($chapter_name, $chapter_id, $bookId, $chapter_idx, $userId = null) { try { if (empty($userId)) { $userId = UserService::instance()->getUserInfo()->id; } if (empty($bookId)) { return $this->getReturn(); } $recentlyReadModel = $this->getUserRecentlyRead(); $urKey = $recentlyReadModel->getURKey($userId);//最近阅读记录zset结构 $ubKey = $recentlyReadModel->getUBKey($userId, $bookId);//最近阅读阅读记录每条的数据:hash结构 $redis = Redis::instance(); //用户阅读章节数,KL使用 $cacheChapter = CacheConstants::getUserChapterCacheKey($userId); $redis->pfAdd($cacheChapter, $chapter_id); $RecentlyReadDB = $this->getUserRecentlyRead()->setConnect($userId); if (empty($userId)) { //游客 $chapterInfo = [ 'id' => $chapter_id, 'name' => $chapter_name, ]; $RecentlyReadDB->recentCookie($bookId, $chapterInfo); //阅读记录记入cookie 只记录5条数据 return $this->getReturn(); } if (!$redis->exists($urKey)) { //如果redis里最近阅读记录为空拉取一下最近阅读记录(防灾) $RecentlyReadDB->getRecentlyRead(0, 10, $userId); } if (!($userRead = $redis->hGetAll($ubKey))) { $userRead = $RecentlyReadDB->getone($userId, $bookId); $userRead = $userRead ? $userRead->toArray() : null; } $userRecentInfo = [ 'chapter_id' => $chapter_id, 'chapter_name' => $chapter_name, 'updatetime' => time(), 'flag' => 1 ]; if (config('site.book_auto_shelf_chapter_num') <= $chapter_idx) { $userRecentInfo['book_shelf_add'] = 1; $userRecentInfo['book_shelf_flag'] = 1; $usKey = $recentlyReadModel->getUSKey($userId); if (!$redis->exists($usKey)) { $this->_getBookShelf(0, 10, $userId); } $redis->zAdd($usKey, $userRecentInfo['updatetime'], $ubKey); $redis->expire($usKey, 43200); $this->delRecommendShelfBookByBookId($bookId, $userId); } if (!empty($userRead)) { //更新 $getId = $userRead['id']; $RecentlyReadDB->where(['id' => $getId])->update($userRecentInfo); $userRecentInfo = array_merge($userRead, $userRecentInfo);//构造redis用的数据 } else {//创建阅读记录 $userRecentInfo['user_id'] = $userId; $userRecentInfo['book_id'] = $bookId; $userRecentInfo['createtime'] = time(); $RecentlyReadDB->insert($userRecentInfo); $id = $RecentlyReadDB->getLastInsID(); $userRecentInfo['id'] = $id; if (!$redis->exists($urKey)) { //(如果redis里最近阅读记录为空拉取一下最近阅读记录防灾) $RecentlyReadDB->getRecentlyRead(time() + 1, 10, $userId); } } $redis->hmset($ubKey, $userRecentInfo); $redis->expire($ubKey, 43200); //设置失效时间 $redis->zadd($urKey, $userRecentInfo['updatetime'], $ubKey); $redis->expire($urKey, 43200); //设置失效时间 return $this->getReturn(); } catch (\Exception $e) { return $this->getExceptionReturn($e); } } /** * 获取章节阅读配置 */ public function getReadingSetting() { $return = [ 'read_body_fontsize' => 'read_body_fontsize_2', 'read_theme' => 'read_theme_1', 'day_night' => '', ]; if (!Config::get('template.view_theme')) { $return['read_theme'] = 'read_theme_2'; //西瓜 } else { $return['read_theme'] = 'read_theme_1'; //袋鼠 沙发 美书 } if (Cookie::has('cs')) { $cs['l'] = 1; $cs = json_decode(Cookie::get('cs'), true); //章节排序 l=1正序 l=2倒序 if ($cs['f'] !== null) { //字号 $return['read_body_fontsize'] = 'read_body_fontsize_' . intval($cs['f']); } if ($cs['b'] !== null) { //背景色 $return['read_theme'] = 'read_theme_' . intval($cs['b']); } if ($cs['d'] == 2) { //白天/夜间 主题 $return['day_night'] = 'read_theme_5'; } } return $this->setData($return)->getReturn(); } /** * 获取书籍默认链接 * @param $book_id * @return ReturnObject */ public function getBookIndexPage($book_id) { return $this->setData('/index/book/info?book_id=' . $book_id)->getReturn(); } /** * 随机获取一本返回推荐书籍 * @return ReturnObject */ public function getReturnRecommandBook() { $book_data = false; $recentNum = Env::get('app.return_filter_recent_num', 100); $recentRead = UserService::instance()->getUserRecentlyReadModel()->getRecentlyRead(0, $recentNum); $book_ids = []; if ($recentRead['totalNum'] > 0) { $book_ids = array_column($recentRead['data'], 'book_id'); } $count = $this->getReturnRecommandModel() ->where('status', 'normal') ->whereNotIn('book_id', $book_ids) ->count(); if ($count) { $index = rand(0, $count - 1); $book_list = model('ReturnRecommand') ->where('status', 'normal') ->whereNotIn('book_id', $book_ids) ->limit($index, 1) ->select(); $book = $book_list[0]; $book_data = BookService::instance()->getBookModel()->BookInfo($book['book_id']); if ($book_data) { $book_data['url'] = '/index/book/chapter?book_id=' . $book['book_id'] . '&sid=' . $book_data['first_chapter_id']; } } return $this->setData($book_data)->getReturn(); } /** * @param $bookInfo 书籍信息 * @param $chapterId 章节id * @param $type 类型,0当前章节;1上一章;2、下一章; * @return ReturnObject */ public function getBookChapterInfoForClient($bookInfo, $chapterId, $type) { try { $bookId = $bookInfo['id']; //如果章节id为空,则获取当前书籍第一章的内容 if (empty($chapterId)) { $chapterId = $bookInfo['first_chapter_id']; } if (empty($chapterId)) { return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg('首章id不存在')->getReturn(); } $chapterResult = $this->getChapterInfoForClient($bookId, $chapterId, $type); if ($chapterResult->code != ErrorCodeConstants::SUCCESS) { return $this->setCode($chapterResult->code)->setMsg($chapterResult->msg)->getReturn(); } return $this->setData($chapterResult->data)->getReturn(); } catch (\Exception $e) { LogService::exception($e); $this->setCode(ErrorCodeConstants::EXCEPTION)->getReturn(); } } /** * 获取章节信息 * @param $bookId 书籍id * @param $chapterId 章节id * @param $type 类型,0当前章节;1上一章;2、下一章; * @return ReturnObject */ private function getChapterInfoForClient($bookId, $chapterId, $type) { try { $chapterList = self::getChapterList($bookId, 1, -1); if ($chapterList->code != ErrorCodeConstants::SUCCESS) { LogService::notice('获取章节列表失败!书籍ID:' . $bookId); return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg('章节不存在')->getReturn(); } $data = []; foreach ($chapterList->data['data'] as $key => $chapter) { if ($chapter['id'] == $chapterId) { $idx = $key; switch ($type) { case ClientApiConstants::GET_CHAPTER_TYPE_PRE: $idx = $key - 1; break; case ClientApiConstants::GET_CHAPTER_TYPE_NEXT: $idx = $key + 1; break; default: break; } $chapterInfo = isset($chapterList->data['data'][$idx]) ? $chapterList->data['data'][$idx] : null; if (isset($chapterInfo)) { $chapterInfoId = $chapterInfo['id']; $data['name'] = $chapterInfo['name']; $data['idx'] = $chapterInfo['idx']; $data['id'] = $chapterInfoId; if ($idx == 0) { $data['pre_id'] = ''; } else { $data['pre_id'] = $chapterList->data['data'][$idx - 1]['id']; } if (isset($chapterList->data['data'][$idx + 1])) { $data['next_id'] = $chapterList->data['data'][$idx + 1]['id']; } else { $data['next_id'] = ''; } } break; } } if (empty($data)) { return $this->setCode(ClientApiConstants::CHAPTER_NOT_EXIST)->getReturn(); } $redis = Redis::instance(); $cacheKey = CacheConstants::getBookSingleChapterCacheKey($chapterInfoId); $aChapter = []; if ($redis->exists($cacheKey)) { if (json_decode($redis->get($cacheKey), true)) { $aChapter = json_decode($redis->get($cacheKey), true); } else { $aChapter = $redis->get($cacheKey); $aChapter = str_replace('

', '', $aChapter); $aChapter = explode('

', $aChapter); } } else { $path = Config::get('site.chapter_path'); if (empty($path)) { $path = 'cppartner'; } $file = $this->getChapterContentPath($path, $bookId, $chapterInfoId); LogService::info('章节文件路径:' . $file); if (is_file($file)) { $content = file_get_contents($file); $content = str_replace("\r", '', $content); $arr = explode("\n", $content); foreach ($arr as $value) { $value = ltrim($value, "\0\t\n\\x0B\r  "); $value = mb_convert_encoding($value, "UTF-8", "UTF-8"); //强制转换一次UTF-8编码 if ($value) { $aChapter[] = $value; } } } else { LogService::error('获取章节内容失败!书籍路径:' . $file . ',ID:' . $bookId . ' 章节ID:' . $chapterInfoId); $data['status'] = ClientApiConstants::ORDER_CHAPTER_STATE_NO_CONTENT; } if ($aChapter) { $keyNum = sprintf('BCN:%s:%s', $chapterInfoId, date('i')); if ($redis->incr($keyNum) > 10) { $redis->setex($cacheKey, 3600, json_encode($aChapter, true)); } $redis->expire($keyNum, 60); } } $data['content'] = is_array($aChapter) ? implode("\n", $aChapter) : $aChapter; return $this->setData($data)->getReturn(); } catch (\Exception $e) { LogService::exception($e); return $this->getExceptionReturn($e); } } /** * 获取书籍全部章节信息 * @param $bookId 书籍id * @return ReturnObject */ public function getChapterListForClient($bookId) { $chapters = model("Book")::getChapterLimit($bookId, 0, -1); if (empty($chapters)) { return $this->setCode(ErrorCodeConstants::RESULT_EMPTY)->setMsg('没有获取到目录信息')->getReturn(); } else { return $this->setData($chapters)->getReturn(); } } /** * 获取app使用的推荐书籍id列表 * @param $sex 性别 * @param int $limit * @return array|mixed */ public function getRecommendBookIdsForClient($sex, $limit) { $redis = Redis::instance(); $key = sprintf('%s:%s:%s', ClientApiConstants::RECOMMAND_BOOK_KEY, $sex, $limit); if ($redis->exists($key)) { $data = $redis->get($key); $ids = json_decode($data, true); } else { if ($sex != 0) { $map['sex'] = $sex; } $map['state'] = 1; $ids = model('Book')->where($map)->order("idx", "desc")->limit($limit)->column("id"); $json = json_encode($ids); $redis->setex($key, 900, $json); } return $ids; } /** * 删除最近阅读记录 * @param $recentId * @param $bookId * @param null $userId * @return ReturnObject */ public function removeRecentlyRead_old($recentId, $bookId, $userId = null) { $result = $this->getUserRecentlyRead()->removeBook($recentId, $bookId, $userId); if ($result) { return $this->getReturn(); } else { return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除最近阅读失败')->getReturn(); } } /** * @param $aRecentIds * @param $aBookIds * @param null $userId * @return ReturnObject */ public function removeRecentlyRead($aRecentIds, $aBookIds, $userId = null) { if (!$userId) { $userId = UserService::instance()->getUserInfo()->id; } if (empty($aRecentIds)) { LogService::error('删除最近阅读失败,$aRecentIds为空,userId:' . $userId); return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除最近阅读失败')->getReturn(); } if (empty($aBookIds)) { LogService::error('删除最近阅读失败,$aBookIds为空,userId:' . $userId); return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除最近阅读失败')->getReturn(); } $userRecentModel = $this->getUserRecentlyRead()->setConnect($userId); $dbRes = $userRecentModel->whereIn('id', $aRecentIds)->update(['flag' => 0]); if ($dbRes) { $redis = Redis::instance(); $urKey = $userRecentModel->getURKey($userId); $ubKeys = []; foreach ($aBookIds as $bookId) { $ubKeys[] = $userRecentModel->getUBKey($userId, $bookId); } $redis->zrem($urKey, ...$ubKeys); $getUBKeyRedisIndex = Redis::splitKeysByRule($ubKeys); array_walk($getUBKeyRedisIndex, function ($v, $k) { $redis = Redis::getRedisConnect($k); $redis->del(...$v); }); } return $this->getReturn(); } /** * 获取书籍客户端需要的状态 * @param array $bookInfo 书籍信息 * @param $chapterKey 最后一章id * @return int */ public static function makeBookStatusForClient($bookInfo, $chapterKey = null) { if ($bookInfo['state'] == 0) { return ClientApiConstants::BOOK_STATE_OFF; } if (isset($chapterKey) && $bookInfo['last_chapter_id'] != $chapterKey) { return ClientApiConstants::BOOK_STATE_UPDATE; } $now = time(); if ($now > $bookInfo['free_stime'] && $now < $bookInfo['free_etime']) { return ClientApiConstants::BOOK_STATE_LIMIT_FREE; } return ClientApiConstants::BOOK_STATE_NORMAL; } #region 书架相关 /** * 从最近阅读记录表获取书架信息 * @param int $updatetime * @param int $pageSize * @param bool $userId * @return array */ private function _getBookShelf($updatetime = 0, $pageSize = 10, $userId = false) { if (!$userId) { $userId = UserService::instance()->getUserInfo()->id; } $redis = Redis::instance(); $usKey = $this->getUserRecentlyRead()->getUSKey($userId); if ($updatetime) { $pageBegin = $updatetime - 1; $resUS = $redis->zRevRangeByScore($usKey, $pageBegin, '-inf', ['limit' => [0, $pageSize]]); } else { $pageBegin = 0; $resUS = $redis->zRevRange($usKey, 0, $pageSize - 1); } if (empty($resUS)) { $result = $this->getUserRecentlyRead()->getShelfRecentlyByDB($userId, $pageBegin, $pageSize); } else { $result = $this->getUserRecentlyRead()->getShelfRecentlyByRedis($userId, $resUS); #region 如果U-B中没有book_shelf_add或者book_shelf_flag属性,那么从数据库中获取 // 此处代码上线一段时间后可删除 foreach ($result as $item) { if (!isset($item['book_shelf_add']) || !isset($item['book_shelf_flag'])) { // 将shelf库里的数据刷到阅读记录 $result = $this->getUserRecentlyRead()->getShelfRecentlyByDB($userId, $pageBegin, $pageSize); break; } } #endregion } $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['name'] = $book['name']; $item['state'] = $book['state']; $item['free_stime'] = $book['free_stime']; $item['free_etime'] = $book['free_etime']; $item['chapter_id'] = empty($item['chapter_id']) ? $book['first_chapter_id'] : $item['chapter_id']; $item['last_chapter_id'] = $book['last_chapter_id']; $item['recommend'] = false; } } return $result; } /** * 获取用户的书架 * @return ReturnObject */ public function getBookShelf() { try { // 获取用户加入书架的书籍id $shelfBooks = $this->getRecommendShelfBooks(); $recommendShelfBooks = ArrayHelper::array_column_search($shelfBooks, 'insert_type', BookConstants::SHELF_INSERT_TYPE_RECOMMEND, false); $recommendShelfBookIds = array_column($recommendShelfBooks, 'book_id'); $userInfo = UserService::instance()->getUserInfo(); $userId = $userInfo->id; $redis = Redis::instance(); #region 如果shelf数据库有用户主动加入的书籍,迁移到最近阅读表,并删除shelf表的数据 if (count($shelfBooks) > count($recommendShelfBooks)) { $userRecentlyModel = $this->getUserRecentlyRead()->setConnect($userId); $shelfBookIds = array_column($shelfBooks, 'book_id'); $needToRecentBookIds = array_diff($shelfBookIds, $recommendShelfBookIds); //用户已经阅读过的书籍 $existBookIds = $userRecentlyModel->whereIn('book_id', $needToRecentBookIds)->where('user_id', $userId)->column('book_id'); //修改数据库 if (!empty($existBookIds)) { $userRecentlyModel->whereIn('book_id', $existBookIds)->where('user_id', $userId)->update([ 'book_shelf_add' => 1, 'book_shelf_flag' => 1 ]); } //插入数据库 $unReadBookIds = array_diff($needToRecentBookIds, $existBookIds); $insData = []; foreach ($unReadBookIds as $unReadBookId) { $insData[] = [ 'user_id' => $userId, 'book_id' => $unReadBookId, 'chapter_id' => 0, 'chapter_name' => '', 'flag' => 1, 'book_shelf_add' => 1, 'book_shelf_flag' => 1, 'createtime' => time(), 'updatetime' => time(), ]; } $userRecentlyModel->insertAll($insData); #region 删除redis U-R U-B $redis->del($userRecentlyModel->getURKey($userId)); $ubKeys = []; foreach ($needToRecentBookIds as $needToRecentBookId) { $ubKeys[] = $userRecentlyModel->getUBKey($userId, $needToRecentBookId); } $getUBRedisIndex = Redis::splitKeysByRule($ubKeys); array_walk($getUBRedisIndex, function ($v, $k) { $redis = Redis::getRedisConnect($k); $redis->del(...$v); }); $redis->del($this->_shelfBooksKey($userId)); #endregion //删除书架数据库里的非推荐书籍 $this->getBookShelfModel()->setConnect($userId)->where('user_id', $userId)->whereIn('book_id', $needToRecentBookIds)->update(['flag' => Common::NO]); } #endregion $userRecentlyList = model('UserRecentlyRead')->setConnect($userId)->getRecentlyRead(0, 50, $userId); $userRecentlyList = $userRecentlyList['data']; $channelId = UserService::instance()->getUserChannelId()->data; $isWater = WaterBookService::instance()->showBook($channelId, $userId, Ip::ip()); #region 为推荐书籍增加书籍属性 $recommendShelfBookInfos = $this->getBookModel()->getBooksInfo($recommendShelfBookIds); $recommendSimpleBooksInfo = []; foreach ($recommendShelfBookInfos as $recommendShelfBookInfo) { $userRecentlyItem = ArrayHelper::array_column_search($userRecentlyList, 'book_id', $recommendShelfBookInfo['id']); $recentlyChapterId = empty($userRecentlyItem) ? $recommendShelfBookInfo['first_chapter_id'] : $userRecentlyItem['chapter_id']; $_recommendBookInfo = ArrayHelper::array_column_search($recommendShelfBooks, 'book_id', $recommendShelfBookInfo['id']); $recommendBookUpdatetime = empty($_recommendBookInfo['updatetime']) ? 0 : $_recommendBookInfo['updatetime']; $item = [ 'book_id' => $recommendShelfBookInfo['id'], 'image' => $recommendShelfBookInfo['image'], 'name' => $recommendShelfBookInfo['name'], 'state' => $recommendShelfBookInfo['state'], 'free_stime' => $recommendShelfBookInfo['free_stime'], 'free_etime' => $recommendShelfBookInfo['free_etime'], 'chapter_id' => $recentlyChapterId, 'last_chapter_id' => $recommendShelfBookInfo['last_chapter_id'], 'updatetime' => $recommendBookUpdatetime, 'recommend' => true, ]; if ($isWater && (!isset($recommendShelfBookInfo['classify_white_list']) || $recommendShelfBookInfo['classify_white_list'] != 1)) { $item['state'] = 0; } $recommendSimpleBooksInfo[] = $item; } #endregion // $recentSimpleList = $this->_getBookShelf(0, 100); #region 从最近阅读记录表获取书架信息 $usKey = $this->getUserRecentlyRead()->getUSKey($userId); $pageBegin = 0; $resUS = $redis->zRevRange($usKey, 0, 99); if (empty($resUS)) { $result = $this->getUserRecentlyRead()->getShelfRecentlyByDB($userId, $pageBegin, 100); } else { $result = $this->getUserRecentlyRead()->getShelfRecentlyByRedis($userId, $resUS); } $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['name'] = $book['name']; $item['state'] = $book['state']; if ($isWater && (!isset($book['classify_white_list']) || $book['classify_white_list'] != 1)) { $item['state'] = 0; } $item['free_stime'] = $book['free_stime']; $item['free_etime'] = $book['free_etime']; $item['chapter_id'] = empty($item['chapter_id']) ? $book['first_chapter_id'] : $item['chapter_id']; $item['last_chapter_id'] = $book['last_chapter_id']; $item['recommend'] = false; } } $recentSimpleList = $result; #endregion $booksInfo = array_merge($recommendSimpleBooksInfo, $recentSimpleList); $sortArr = array_column($booksInfo, 'updatetime'); array_multisort($sortArr, SORT_DESC, $booksInfo); #region 书架书籍展示逻辑 $freeLimit = array_keys(BuyMoreService::instance()->getFreeLimitCacheBook($userId)->data); $permanent = FinancialService::instance()->getConsumeModel()->setConnect($userId) ->where('type', BookConstants::BOOK_BILLING_MODEL_BOOK) ->where('kandian', 0) ->where('free_kandian', 0) ->column('book_id'); foreach ($booksInfo as &$item) { if ($item['state'] == 0) {//书籍下架 $item['show_status'] = 0; } elseif (in_array($item['book_id'], $permanent)) {//充值加购,赠送 $item['show_status'] = 1; } elseif ($item['free_stime'] <= time() && $item['free_etime'] >= time()) {//限免 $item['show_status'] = 2; } elseif (in_array($item['book_id'], $freeLimit)) {//充值加购,限免 $item['show_status'] = 5; } elseif ($userInfo->vip_endtime > time() && !VipCpService::instance()->isFreeCpBookForVip($item['book_id'], $userId, $userInfo->vip_endtime)) { // vip 用户进行 CP书籍,对VIP收费 $item['show_status'] = 11; } elseif ($item['recommend'] == true) {//推荐 $item['show_status'] = 3; } elseif ($item['chapter_id'] < $item['last_chapter_id']) {//更新 $item['show_status'] = 4; } elseif (in_array($item['book_id'], $freeLimit)) {//充值加购,限免 $item['show_status'] = 5; } else { $item['show_status'] = 10; } } #endregion return $this->setCode(ErrorCodeConstants::SUCCESS)->setData($booksInfo)->getReturn(); } catch (\Exception $e) { return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg($e->getMessage())->getReturn(); } } /** * 是否展示 '非VIP书籍' 角标 * @param $book_id * @return bool */ public function showCpVipSubScript($book_id) { return false; /*$userInfo = UserService::instance()->getUserInfo(); $vip_endtime = $userInfo->vip_endtime; if ($vip_endtime > time()) { if (VipCpService::instance()->isFreeCpBookForVip($book_id, $userInfo->id, $vip_endtime)) { return false; } else { return true; } } else { return false; }*/ } /** * 获取书籍角标, 角标优先级:下架 -> 限免 -> 付费书籍 -> 推荐 * @param array $bookinfo * @return int */ /*public function getBookSubscript(array $bookinfo) { $show_status = -1; $userInfo = UserService::instance()->getUserInfo(); $userId = $userInfo->id; $vip_endtime = $userInfo->vip_endtime; $bookinfo['recommend'] = true; $freeLimit = array_keys(BuyMoreService::instance()->getFreeLimitCacheBook($userId)->data); $permanent = FinancialService::instance()->getConsumeModel()->setConnect($userId) ->where('type', BookConstants::BOOK_BILLING_MODEL_BOOK) ->where('kandian', 0) ->where('free_kandian', 0) ->column('book_id'); if ($bookinfo['state'] == 0) {//书籍下架 $show_status = 0; } elseif (in_array($bookinfo['id'], $permanent)) {//充值加购,赠送 $show_status = 1; } elseif ($bookinfo['free_stime'] <= time() && $bookinfo['free_etime'] >= time()) {//限免 $show_status = 2; } elseif (in_array($bookinfo['id'], $freeLimit)) {//充值加购,限免 $show_status = 5; } elseif ($vip_endtime > time()) { // vip 用户进行 if (!VipCpService::instance()->isFreeCpBookForVip($bookinfo['id'], $userId, $vip_endtime)) { // CP书籍,对VIP收费 $show_status = 11; } } elseif ($bookinfo['recommend'] == true) {//推荐 $show_status = 3; } elseif ($bookinfo['chapter_id'] < $bookinfo['last_chapter_id']) {//更新 $show_status = 4; } elseif (in_array($bookinfo['id'], $freeLimit)) {//充值加购,限免 $show_status = 5; } else { $show_status = 10; } return $show_status; }*/ /** * 获取用户加入书架书籍信息列表 * @param $userId * @return array|mixed */ private function getRecommendShelfBooks($userId = null) { if (empty($userId)) { $userInfo = UserService::instance()->getUserInfo(); $userId = $userInfo->id; } $key = $this->_shelfBooksKey($userId); $redis = Redis::instance(); if ($strData = $redis->get($key)) { $shelfBooks = json_decode($strData, true); } else { $shelfBooks = $this->getBookShelfModel()->setConnect($userId) ->where('user_id', $userId) ->where('flag', Common::YES) ->order('id') ->field(['id', 'book_id', 'updatetime', 'insert_type']) ->select(); $strData = json_encode($shelfBooks); $redis->setex($key, 86400, $strData); } return $shelfBooks; } /** * 删除用户推荐库中的推荐书籍 * @param $bookId * @param $userId */ private function delRecommendShelfBookByBookId($bookId, $userId) { $recommendBooks = $this->getRecommendShelfBooks($userId); $recmdShelfBookInfo = ArrayHelper::array_column_search($recommendBooks, 'book_id', $bookId); if (!empty($recmdShelfBookInfo)) { $key = $this->_shelfBooksKey($userId); $redis = Redis::instance(); $this->getBookShelfModel()->setConnect($userId) ->where('id', $recmdShelfBookInfo['id']) ->update(['flag' => Common::NO]); $recommendBooksDeleted = []; foreach ($recommendBooks as $item) { if ($item['id'] == $recmdShelfBookInfo['id']) { continue; } $recommendBooksDeleted[] = $item; } $strData = json_encode($recommendBooksDeleted); $redis->setex($key, 86400, $strData); } } /** * 获取书架展示书库的书籍id列表 * @return array|mixed */ public function getShelfRecommendBookIds($sex,$isWater=false) { $sex = $sex == 0 ? 1 : $sex; $key = $this->_shelfRecommendBookIdsKey($sex,$isWater); $redis = Redis::instance(); if ($strData = $redis->get($key)) { $ids = json_decode($strData, true); } else { $waterWhere = $isWater ? ' and classify_white_list=1' : ''; $obj = model('BookshelfRecommand')->alias('bsf'); $obj->join('book','book.id=book_id and book.state=1'.$waterWhere,'inner'); $ids = $obj ->where('bsf.sex', $sex)->where('bsf.status', Common::STATUS_NORMAL)->limit(6)->column('bsf.book_id'); $strData = json_encode($ids); $redis->set($key, $strData); $redis->expire($key,900); } return $ids; } /** * 书籍推广书籍插入用户书架 * @param $userId * @param $sex * @return bool */ public function insertShelfRecommendBooks($userId, $sex,$channelId) { try { $channelId = AdminService::instance()->getAdminExtendModel()->getChannelId($channelId); $isWater = WaterBookService::instance()->showBook($channelId,$userId); $bookIds = $this->getShelfRecommendBookIds($sex, $isWater); if (empty($bookIds)) { return false; } $data = []; foreach ($bookIds as $bookId) { $data[] = [ 'user_id' => $userId, 'book_id' => $bookId, 'insert_type' => BookConstants::SHELF_INSERT_TYPE_RECOMMEND, 'createtime' => time(), 'updatetime' => time(), ]; } $this->getBookShelfModel()->setConnect($userId)->insertAll($data); return true; } catch (\Exception $e) { LogService::error($e->getMessage()); } } /** * 清除书架展示书库缓存 */ public function clearShelfRecommendBookIds() { try { $redis = Redis::instance(); $key = $this->_shelfRecommendBookIdsKey(1); $redis->del($key); $key = $this->_shelfRecommendBookIdsKey(2); $redis->del($key); $key = $this->_shelfRecommendBookIdsKey(1,true); $redis->del($key); $key = $this->_shelfRecommendBookIdsKey(1,true); $redis->del($key); return $this->getReturn(); } catch (\Exception $e) { return $this->setCode(ErrorCodeConstants::EXCEPTION)->setData($e->getMessage())->getReturn(); } } /** * 清除文末书籍推荐缓存 */ public function clearNovelEndRecommendBookIds() { try { $redis = Redis::instance(); $key = $this->_novelEndRecommendBookIdsKey(1); $redis->del($key); $key = $this->_novelEndRecommendBookIdsKey(2); $redis->del($key); $redis->del('NERC:W:' . 1); $redis->del('NERC:W:' . 2); return $this->getReturn(); } catch (\Exception $e) { return $this->setCode(ErrorCodeConstants::EXCEPTION)->setData($e->getMessage())->getReturn(); } } /** * 构造用户加入书架书籍id缓存的key * @param $userId * @return string */ private function _shelfBooksKey($userId) { return 'SBIK:' . $userId; } /** * 构造书架推荐书籍id缓存的key * @param $sex * @return string */ private function _shelfRecommendBookIdsKey($sex,$isWater=false) { return $isWater ? 'BEHD:W:' . $sex : 'BEHD:' . $sex; } /** * 构造文末书架推荐书籍id缓存的key * @param $sex * @return string */ private function _novelEndRecommendBookIdsKey($sex) { return 'NERC:' . $sex; } /** * 加入书架 * @param $bookId string 书籍id * @param $userId * @return ReturnObject */ public function setBookShelf($bookId, $userId = null) { try { if (empty($userId)) { $userId = UserService::instance()->getUserInfo()->id; } $recentlyReadModel = $this->getUserRecentlyRead(); $usKey = $recentlyReadModel->getUSKey($userId); $ubKey = $recentlyReadModel->getUBKey($userId, $bookId); $redis = Redis::instance(); $RecentlyReadDB = $this->getUserRecentlyRead()->setConnect($userId); if (!($userRead = $redis->hGetAll($ubKey))) { $userRead = $RecentlyReadDB->getone($userId, $bookId); $userRead = $userRead ? $userRead->toArray() : null; } $userRecentInfo = [ 'updatetime' => time(), 'book_shelf_add' => 1, 'book_shelf_flag' => 1, 'flag' => 0,//用户主动加入书架,阅读记录不生效 ]; if (!empty($userRead)) { //更新 $getId = $userRead['id']; $RecentlyReadDB->where(['id' => $getId])->update($userRecentInfo); $userRecentInfo = array_merge($userRead, $userRecentInfo);//构造redis用的数据 } else {//创建阅读记录 $userRecentInfo['user_id'] = $userId; $userRecentInfo['book_id'] = $bookId; $userRecentInfo['createtime'] = time(); $userRecentInfo['chapter_id'] = 0; $userRecentInfo['chapter_name'] = ""; $RecentlyReadDB->insert($userRecentInfo); $id = $RecentlyReadDB->getLastInsID(); $userRecentInfo['id'] = $id; if (!$redis->exists($usKey)) { //(如果redis里最近阅读记录为空拉取一下最近阅读记录防灾) $RecentlyReadDB->getRecentlyRead(time() + 1, 10, $userId); } } $redis->hMSet($ubKey, $userRecentInfo); $redis->expire($ubKey, 43200); $redis->zAdd($usKey, $userRecentInfo['updatetime'], $ubKey); $redis->expire($usKey, 43200); $this->delRecommendShelfBookByBookId($bookId, $userId); return $this->getReturn(); } catch (\Exception $e) { return $this->getExceptionReturn($e); } } /** * 用户删除书架上的书籍 * @param array $bookIds 书籍id列表 * @return ReturnObject */ public function delBookShelf($bookIds) { if (empty($bookIds)) { return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除书籍id为空')->getReturn(); } $userInfo = UserService::instance()->getUserInfo(); $userId = $userInfo->id; $userRecentModel = $this->getUserRecentlyRead(); try { #region 书架分库里删除数据 $this->getBookShelfModel()->setConnect($userId)->where('user_id', $userId) ->whereIn('book_id', $bookIds) ->where('flag', Common::YES) ->update(['flag' => Common::NO, 'updatetime' => time()]); //删除书架书籍id列表缓存 & 删除书架排除书籍id列表缓存 $redis = Redis::instance(); $shelfBookIdsKey = $this->_shelfBooksKey($userId); $redis->del($shelfBookIdsKey); #endregion #region 阅读记录表里删除数据 $recentlyRead = $userRecentModel->setConnect($userId); $recentlyRead->where('user_id', $userId)->whereIn('book_id', $bookIds)->update([ 'book_shelf_add' => 1, 'book_shelf_flag' => 0 ]); #endregion #region 删除U-B的数据 $ubKeys = []; foreach ($bookIds as $bookId) { $ubKeys[] = $userRecentModel->getUBKey($userId, $bookId); } $getUBRedisIndex = Redis::splitKeysByRule($ubKeys); array_walk($getUBRedisIndex, function ($v, $k) { $redis = Redis::getRedisConnect($k); $redis->del(...$v); }); #endregion #region 在U-S中删除U-B的元素 $usKey = $userRecentModel->getUSKey($userId); $redis->zRem($usKey, ...$ubKeys); #endregion return $this->getReturn(); } catch (\Exception $e) { return $this->setCode(ErrorCodeConstants::EXCEPTION)->getReturn(); } } /** * 检查用户是否将书籍加入书架 * @param $bookId * @return ReturnObject */ public function checkBookInShelf($bookId) { try { $shelfBooks = $this->getRecommendShelfBooks(); $result = ArrayHelper::array_column_search($shelfBooks, 'book_id', $bookId); if (!empty($result)) { return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(true)->getReturn(); } $userInfo = UserService::instance()->getUserInfo(); $userId = $userInfo->id; $usKey = $this->getUserRecentlyRead()->getUSKey($userId); $ubKey = $this->getUserRecentlyRead()->getUBKey($userId, $bookId); $redis = Redis::instance(); $score = $redis->zScore($usKey, $ubKey); if (empty($score)) { $bookShelfList = $this->_getBookShelf(0, 100); $match = ArrayHelper::array_column_search($bookShelfList, 'book_id', $bookId); if (empty($match)) { return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(false)->getReturn(); } else { return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(true)->getReturn(); } } else { return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(true)->getReturn(); } } catch (\Exception $e) { return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg($e->getMessage())->getReturn(); } } /** * 获取当前渠道商是否展示书架功能 * @return bool */ public function showBookShelfFun() { /*$showBookShelf = false; $bookShelfChannelWhitelist = config('site.book_shelf_channel_whitelist'); if ($bookShelfChannelWhitelist == '-1') { $showBookShelf = true; } elseif ($bookShelfChannelWhitelist == '0') { $showBookShelf = false; } else { $aBookShelfChannelWhite = explode(',', $bookShelfChannelWhitelist); $channel_id = UserService::instance()->getUserInfo()->channel_id; if (in_array($channel_id, $aBookShelfChannelWhite)) { $showBookShelf = true; } } return $showBookShelf;*/ $channel_id = UserService::instance()->getUserInfo()->channel_id; return model("ChannelSpecialManage")->isWhite("show_shelf", $channel_id); } #endregion /** * 获取章末推荐书籍 * @param $book_id * @param $chapter_id * @return ReturnObject */ public function getChapterEndRecommendBook($book_id, $chapter_id) { $return = false; $cacheKey = CacheConstants::getChapterEndRecommendIds($book_id); $book_info = []; if ($list = Redis::instance()->zRangeByScore($cacheKey, 0, $chapter_id)) { foreach ($list as $index=>$item) { $book_info[$index] = json_decode($item, true); } } else { $list = $this->getChapterEndRecommendModel() ->alias('ce') ->join('book', 'book.id=ce.relation_book_id') ->where('book.state', BookConstants::BOOK_STATE_ON_SALE) ->where('ce.book_id', $book_id) ->where('ce.status', 'normal') ->field('ce.relation_book_id, ce.chapter_show, ce.recommend, ce.tip') ->select(); if ($list) { foreach ($list as $item) { Redis::instance()->zAdd($cacheKey, $item['chapter_show'], json_encode($item->getData(), JSON_UNESCAPED_UNICODE)); } foreach ($list as $index=>$item) { $book_info[$index] = $item->getData(); } } else { Redis::instance()->zAdd($cacheKey, 0, '{"chapter_show":0}'); } Redis::instance()->expire($cacheKey, 600); } $end_close = Cookie::get('chapter_end-'.$book_id); if ($end_close) { $close_ids = explode('.', $end_close); } else { $close_ids = []; } $book_info = array_filter($book_info, function ($v) use ($chapter_id, $close_ids) { return $v['chapter_show'] && $v['chapter_show'] <= $chapter_id && !in_array($v['relation_book_id'], $close_ids); }); if ($book_info) { $random = ArrayHelper::array_random($book_info); $mBook = \app\common\service\BookService::instance()->getBookModel(); $book = $mBook->BookInfo($random['relation_book_id']); if ($book) { $return = array_merge($book, $random); if (!$return['recommend']) { $return['recommend'] = $book['description']; } if (mb_strlen($return['recommend']) > 30) { $return['recommend'] = mb_substr($return['recommend'], 0, 30) . '...'; } } } return $this->setData($return)->getReturn(); } /* * 榜单列表 rank1男频 rank2女频 rank0不区分男女 按照idx排序 */ public function ranklist($type, $is_water) { if($is_water){ $join = ' and book.classify_white_list=1'; $key0 = 'RANK:0:W'; $key1 = 'RANK:1:W'; $key2 = 'RANK:2:W'; }else{ $join = ''; $key0 = 'RANK:0'; $key1 = 'RANK:1'; $key2 = 'RANK:2'; } $boydata = []; $girldata = []; if (Redis::instance()->exists($key1) && Redis::instance()->exists($key2) && Redis::instance()->exists($key0)) { $boydata = json_decode(Redis::instance()->get($key1), true); $girldata = json_decode(Redis::instance()->get($key2), true); $idxdata = json_decode(Redis::instance()->get($key0), true); } else { $boydata['click'] = collection( model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT') ->where(['book.state' => 1, 'book.sex' => 1]) ->field('book.*,bc.name as bc_name') ->order('book.read_num desc') ->limit(10) ->select() )->toArray(); foreach ($boydata['click'] as &$value) { $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']); $value['read_nums'] = friend_date($value['read_num']); $value['author'] = $value['bc_name']; } $boydata['idx'] = collection( model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT') ->where(['book.state' => 1, 'book.sex' => 1]) ->field('book.*,bc.name as bc_name') ->order('book.idx desc') ->limit(10) ->select() )->toArray(); foreach ($boydata['idx'] as &$value) { $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']); $value['read_nums'] = friend_date($value['read_num']); $value['author'] = $value['bc_name']; } $girldata['click'] = collection( model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT') ->where(['book.state' => 1, 'book.sex' => 2]) ->field('book.*,bc.name as bc_name') ->order('book.read_num desc') ->limit(10) ->select() )->toArray(); foreach ($girldata['click'] as &$value) { $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']); $value['read_nums'] = friend_date($value['read_num']); $value['author'] = $value['bc_name']; } $girldata['idx'] = collection( model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT') ->where(['book.state' => 1, 'book.sex' => 2]) ->field('book.*,bc.name as bc_name') ->order('book.idx desc') ->limit(10) ->select() )->toArray(); foreach ($girldata['idx'] as &$value) { $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']); $value['read_nums'] = friend_date($value['read_num']); $value['author'] = $value['bc_name']; } $idxdata['idx'] = collection( model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT') ->where(['book.state' => 1]) ->field('book.*,bc.name as bc_name') ->order('book.idx desc') ->limit(10) ->select() )->toArray(); foreach ($idxdata['idx'] as &$value) { $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']); $value['read_nums'] = friend_date($value['read_num']); $value['author'] = $value['bc_name']; } if (!empty($boydata)) { Redis::instance()->setex($key1, 600, json_encode($boydata, JSON_UNESCAPED_UNICODE)); } if (!empty($girldata)) { Redis::instance()->setex($key2, 600, json_encode($girldata, JSON_UNESCAPED_UNICODE)); } if (!empty($idxdata)) { Redis::instance()->setex($key0, 600, json_encode($idxdata, JSON_UNESCAPED_UNICODE)); } } if ($type == 1) { return $boydata; } elseif ($type == 2) { return $girldata; } elseif ($type == 'boy') { return $boydata; } elseif ($type == 'girl') { return $girldata; } else { return $idxdata; } } /* * 主编推荐列表 */ public function recommendList($book_id) { if($book_id){ $bookInfo = model("book")->BookInfo($book_id); }else{ return false; } $where = []; $where['state'] = '1'; $where['book_category_id'] = $bookInfo['book_category_id']; //最近阅读 $recentList = model('UserRecentlyRead')->getRecentlyRead(0); $recentIds[] = $bookInfo['id']; if ($recentList['totalNum'] > 0) { foreach ($recentList['data'] as $v) { $recentIds[] = $v['book_id']; } } if ($recentIds) { $where['id'] = ['not in', $recentIds]; } $pagedata[0]['name'] = "主编推荐"; $pagedata[0]['page_id'] = 1; $pagedata[0]['second_name'] = "主编推荐"; $pagedata[0]['block_resource'] = collection(model("Book") ->where($where) ->order('rand()') ->limit(4) ->select())->toArray(); if($pagedata[0]['block_resource']){ foreach ($pagedata[0]['block_resource'] as &$v){ $v['book_id'] = $v['id']; } } return $pagedata; } }