BookService.php 80 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Bear
  5. * Date: 2018/11/29
  6. * Time: 下午4:39
  7. */
  8. namespace app\main\service;
  9. use app\common\constants\Common;
  10. use app\common\library\Ip;
  11. use app\common\library\Redis;
  12. use app\common\model\Book;
  13. use app\common\model\BookLimit;
  14. use app\common\model\ChapterEdited;
  15. use app\common\model\Chapterendrecommend;
  16. use app\common\model\FollowRecommand;
  17. use app\common\model\Guide;
  18. use app\common\model\GuideWx;
  19. use app\common\model\ReturnRecommand;
  20. use app\common\model\UserRecentlyRead;
  21. use app\common\service\VipCpService;
  22. use app\common\service\WaterBookService;
  23. use app\main\constants\BookConstants;
  24. use app\main\constants\CacheConstants;
  25. use app\main\constants\ClientApiConstants;
  26. use app\main\constants\ErrorCodeConstants;
  27. use app\main\helper\ArrayHelper;
  28. use app\main\model\object\ReturnObject;
  29. use think\Config;
  30. use think\Cookie;
  31. use think\Env;
  32. class BookService extends BaseService
  33. {
  34. /**
  35. * @var BookService
  36. */
  37. protected static $self = NULL;
  38. /**
  39. * @return BookService
  40. */
  41. public static function instance()
  42. {
  43. if (self::$self == NULL) {
  44. self::$self = new self();
  45. }
  46. return self::$self;
  47. }
  48. /**
  49. * @return Book
  50. */
  51. public function getBookModel()
  52. {
  53. return model('Book');
  54. }
  55. /**
  56. * @return UserRecentlyRead
  57. */
  58. public function getUserRecentlyRead()
  59. {
  60. return model('UserRecentlyRead');
  61. }
  62. /**
  63. * @return FollowRecommand
  64. */
  65. public function getFollowRecommandModel()
  66. {
  67. return model('FollowRecommand');
  68. }
  69. /**
  70. * @return ReturnRecommand
  71. */
  72. public function getReturnRecommandModel()
  73. {
  74. return model('ReturnRecommand');
  75. }
  76. /**
  77. * @return BookLimit
  78. */
  79. public function getBookLimit()
  80. {
  81. return model('BookLimit');
  82. }
  83. /**
  84. * @return Guide
  85. */
  86. public function getGuideModel()
  87. {
  88. return model('Guide');
  89. }
  90. /**
  91. * @return GuideWx
  92. */
  93. public function getGuideWxModel()
  94. {
  95. return model('GuideWx');
  96. }
  97. /**
  98. * @return ChapterEdited
  99. */
  100. public function getChapterEditedModel()
  101. {
  102. return model('ChapterEdited');
  103. }
  104. /**
  105. * @return \app\common\model\BookShelf
  106. */
  107. public function getBookShelfModel()
  108. {
  109. return model('BookShelf');
  110. }
  111. /**
  112. * @return Chapterendrecommend
  113. */
  114. public function getChapterEndRecommendModel()
  115. {
  116. return model('Chapterendrecommend');
  117. }
  118. /**
  119. * @param $admin_id
  120. * @return string
  121. */
  122. public function getEventTextNoResult($admin_id)
  123. {
  124. $site_url = getCurrentDomain($admin_id);
  125. trim($site_url, '/');
  126. $msg = implode("\n\n", [
  127. "对不起,找不到相关的小说",
  128. "您可以试试:",
  129. "1、<a href=\"{$site_url}/index/user/recent\">查看阅读记录>></a>",
  130. "2、<a href=\"{$site_url}/index/index/search\">精准找书>></a>",
  131. "3、<a href=\"{$site_url}\">去首页看看>></a>",
  132. ]);
  133. return $msg;
  134. }
  135. /**
  136. * @param $message
  137. * @return bool|mixed
  138. * @throws \Exception
  139. * @throws \think\db\exception\DataNotFoundException
  140. * @throws \think\db\exception\ModelNotFoundException
  141. * @throws \think\exception\DbException
  142. */
  143. public function getWeinxinSearchBooks($message,$is_water, $admin_id)
  144. {
  145. $search = $message->Content;
  146. $openid = $message->FromUserName;
  147. $return = false;
  148. $type = 0; //0 精准匹配 1 推荐
  149. if (trim($search)) {
  150. $search = addslashes($search);
  151. $searchKey = 'SEB:' . $search;
  152. $issetBookSearch = true;
  153. if (Redis::instance()->exists($searchKey)) {
  154. $issetBookSearch = false;
  155. $redData = Redis::instance()->get($searchKey);
  156. $booksData = json_decode($redData, true);
  157. $books = $booksData['books'];
  158. $total = $booksData['total'];
  159. LogService::info('redis'.json_encode($books));
  160. } else {
  161. $where = [];
  162. $where['state'] = 1;
  163. $where['name|keywords'] = ['like', '%'.addslashes($search).'%'];
  164. if ($is_water) {
  165. $where['classify_white_list'] = 1;
  166. }
  167. //模糊查询的排序-- 优先推送全匹配的书籍,所以只取一本即可 wdd 191028
  168. $order = "(case
  169. when name = '".$search."' then 0
  170. when name like '".$search."%' then 1
  171. when name like '%".$search."' then 2
  172. when name like '%".$search."%' then 3
  173. else 4
  174. end) asc";
  175. // $total = BookService::instance()->getBookModel()->where($where)->count();
  176. $books = BookService::instance()->getBookModel()->where($where)->page(0, 1)->order($order)->select();
  177. }
  178. if ( !empty($books) ) {
  179. //判断来源是redis里的还是数据库
  180. if ($issetBookSearch) {
  181. $books = array_pop($books);
  182. LogService::info('数据库'.json_encode($books));
  183. $saveReData = [];
  184. $saveReData['books'] = $books;
  185. $saveReData['total'] = 1;
  186. Redis::instance()->setex($searchKey, 86400, json_encode($saveReData));
  187. }
  188. $return[0] = $books;
  189. } else {
  190. //没有搜到书 从关键字回复书库取
  191. $type = 1;
  192. $where = [];
  193. $where['book.state'] = ['eq','1'];
  194. $where['gzhreply_recommand.status'] = ['eq','normal'];
  195. if ($is_water) {
  196. $where['classify_white_list'] = 1;
  197. }
  198. if ($uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($admin_id, $openid)) {
  199. $soruceReadLog = model('UserRecentlyRead')->setConnect($uid)->getRecentlyRead(0,10,$uid);
  200. if ($soruceReadLog['totalNum'] > 0) {
  201. foreach ($soruceReadLog['data'] as $val) {
  202. $bookIds[] = $val['book_id'];
  203. }
  204. $bookIdsToStr = implode(',', $bookIds);
  205. $where['gzhreply_recommand.book_id'] = ['not in', $bookIdsToStr];
  206. }
  207. $userInfo = model('User')->getUserInfo($uid);
  208. if(empty($userInfo['sex'])){
  209. $where['gzhreply_recommand.sex'] = ['eq','1'];
  210. }else{
  211. $where['gzhreply_recommand.sex'] = ['eq',$userInfo['sex']];
  212. }
  213. $totalNum = model('GzhreplyRecommand')->join('book', 'book.id = gzhreply_recommand.book_id')->where($where)->count();
  214. $randNum = $totalNum > 3 ? $totalNum-3 : 0;
  215. if($randNum>0){
  216. $randNum = mt_rand(0,$randNum);
  217. }
  218. $result = model('GzhreplyRecommand')->join('book', 'book.id = gzhreply_recommand.book_id')->field('book.*')->where($where)->limit($randNum,3)->select();
  219. if (!empty($result)) {
  220. $return = $result;
  221. }
  222. }
  223. }
  224. }
  225. return [$type, $return];
  226. }
  227. /**
  228. * 合并游客阅读记录 @todo 已废弃
  229. * @throws \Exception
  230. */
  231. public function mergeGuestReadingRecord()
  232. {
  233. if (UserService::instance()->isLogin() && Cookie::has('read')) {
  234. $readTmp = Cookie::get('read');
  235. $userId = UserService::instance()->getUserInfo()->id;
  236. foreach ($readTmp as $key => $val) {
  237. $book = BookService::instance()->getBookModel()->bookinfo($key);
  238. $bookInfo = [];
  239. $bookInfo['image'] = $book['image'];
  240. $bookInfo['book_name'] = $book['name'];
  241. $bookInfo['last_chapter_name'] = $book['last_chapter_name'];
  242. $bookInfo['last_chapter_utime'] = $book['last_chapter_utime'];
  243. $bookInfo['state'] = $book['state'];
  244. $insertData = [];
  245. $insertData['user_id'] = $userId;
  246. $insertData['book_id'] = $val['book_id'];
  247. $insertData['chapter_id'] = $val['chapter_id'];
  248. $insertData['chapter_name'] = $val['chapter_name'];
  249. $insertData['createtime'] = time();
  250. $insertData['updatetime'] = $val['updatetime'];
  251. UserService::instance()->getUserRecentlyReadModel()->setConnect($userId)->insert($insertData);
  252. $id = UserService::instance()->getUserRecentlyReadModel()->setConnect($userId)->getLastInsID();
  253. $insertData['id'] = $id;
  254. $insertArr = array_merge($insertData, $bookInfo);
  255. $key = 'U-R:' . $userId;
  256. $hkey = 'U-B:' . $userId . ':' . $val['book_id'];
  257. Redis::instance()->hmset($hkey, $insertArr);
  258. Redis::instance()->zrem($key, $hkey);
  259. Redis::instance()->zadd($key, $insertArr['updatetime'], $hkey);
  260. Redis::instance()->expire($key, 43200);
  261. Redis::instance()->expire($hkey, 43200);
  262. }
  263. Cookie::delete('read');
  264. }
  265. }
  266. /**
  267. * 获取书籍与当前章节信息
  268. * @param $book_id
  269. * @param $chapter_id
  270. * @param $sid
  271. * @return \app\main\model\object\ReturnObject
  272. */
  273. public function getBookChapterInfo($book_id, $chapter_id, $sid = '', $isWater=false)
  274. {
  275. try{
  276. //参数错误
  277. if (!$book_id) {
  278. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData('/')->getReturn();
  279. }
  280. $bookInfoRedirect = $this->getBookIndexPage($book_id)->data;
  281. //书籍不存在
  282. $bookinfo = BookService::instance()->getBookModel()->BookInfo($book_id);
  283. if ($isWater && (!isset($bookinfo['classify_white_list']) || $bookinfo['classify_white_list'] !=1)){//清水逻辑 按照下架处理
  284. return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([
  285. 'tpl' => 'public/forbiddenread',
  286. 'bind' => [
  287. 'book' => $bookinfo,
  288. ],
  289. ])->getReturn();
  290. }
  291. if (!$bookinfo) {
  292. return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData(['tpl'=>'public/nobook'])->getReturn();
  293. }
  294. if($bookinfo['recommand_book_id']){
  295. $recmooand = BookService::instance()->getBookModel()->BookInfo($bookinfo['recommand_book_id']);
  296. if ($recmooand) {
  297. $bookinfo['recommand_book_image'] =$recmooand['image'];
  298. $bookinfo['recommand_book_name'] =$recmooand['name'];
  299. }else{
  300. $bookinfo['recommand_book_image'] = '';
  301. $bookinfo['recommand_book_name'] = '';
  302. }
  303. }
  304. //书籍已下架
  305. if ($bookinfo['state'] == BookConstants::BOOK_STATE_OFF_SALE) {
  306. return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([
  307. 'tpl' => 'public/forbiddenread',
  308. 'bind' => [
  309. 'book' => $bookinfo,
  310. ],
  311. ])->getReturn();
  312. }
  313. if (!$chapter_id) {
  314. if (!$chapter_id && UserService::instance()->isLogin()) {
  315. $user_id = UserService::instance()->getUserInfo()->id;
  316. $readlog = $this->getUserRecentlyRead()->getone($user_id, $book_id);
  317. if ($readlog) {
  318. $chapter_id = $readlog['chapter_id'];
  319. }
  320. }
  321. if (!$chapter_id) {
  322. if ($sid) {
  323. $chapter_id = $sid;
  324. } else {
  325. $chapter_id = $bookinfo['first_chapter_id'];
  326. }
  327. }
  328. }
  329. //章节参数缺失
  330. if (!$chapter_id) {
  331. $this->setCode(ErrorCodeConstants::REDIRECT)->setData($bookInfoRedirect)->getReturn();
  332. }
  333. $chapter_data = $this->getChapterInfo($book_id, $chapter_id);
  334. //章节异常
  335. if ($chapter_data->code == ErrorCodeConstants::EXCEPTION) {
  336. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($bookInfoRedirect)->getReturn();
  337. }
  338. //章节缺失
  339. if ($chapter_data->code == ErrorCodeConstants::RESULT_EMPTY) {
  340. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($bookInfoRedirect)->getReturn();
  341. }
  342. $chapter = $chapter_data->data;
  343. if(is_array($chapter['content'])){
  344. $content = array_map(function($item){
  345. return '<p>'.$item.'</p>';
  346. },$chapter['content']);
  347. $chapter['content'] = implode('', $content);
  348. }
  349. //空内容,无下一章
  350. if (!strip_tags($chapter['content']) && !isset($chapter['next_id'])) {
  351. return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([
  352. 'tpl' => 'public/nobook',
  353. 'bind' => [
  354. 'book' => $bookinfo,
  355. ],
  356. ])->getReturn();
  357. }
  358. //空内容,有下一章
  359. if (!strip_tags($chapter['content']) && isset($chapter['next_id'])) {
  360. return $this->setCode(ErrorCodeConstants::REDIRECT_VIEW)->setData([
  361. 'tpl' => 'public/nocontent',
  362. 'bind' => [
  363. 'book' => $bookinfo,
  364. 'chapter' => $chapter,
  365. 'next_link' => '/index/book/chapter?book_id=' . $book_id . '&chapter_id=' . $chapter['next_id'],
  366. ],
  367. ])->getReturn();
  368. }
  369. $preid = 0;
  370. if (empty($chapter['pre_id'])) {
  371. $pre_link = "javascript:consoleMain('已经是第一章了');";
  372. } else {
  373. $pre_link = '/index/book/chapter?book_id=' . $book_id . '&chapter_id=' . $chapter['pre_id'];
  374. $preid = $chapter['pre_id'];
  375. }
  376. if ($chapter['next_id']) {
  377. $next_link = '/index/book/chapter?book_id=' . $book_id . '&chapter_id=' . $chapter['next_id'];
  378. } else {
  379. if ($bookinfo['is_finish'] == BookConstants::BOOK_IS_FINISH_YES) {
  380. $next_link = '/index/book/statuspage/code/2/book_id/' . $book_id;
  381. } else {
  382. $next_link = '/index/book/statuspage/code/1/book_id/' . $book_id;
  383. }
  384. }
  385. $data = [
  386. 'chapter' => $chapter,
  387. 'book' => $bookinfo,
  388. 'pre_link' => $pre_link,
  389. 'next_link' => $next_link,
  390. 'pre_id' => $preid
  391. ];
  392. return $this->setData($data)->getReturn();
  393. }catch (\Exception $e){
  394. return $this->getExceptionReturn($e);
  395. }
  396. }
  397. /**
  398. * 获取章节信息
  399. *
  400. * @param int $book_id 书籍ID
  401. * @param int $page_no 第几页
  402. * @param int $limit 单页条数
  403. * @return \app\main\model\object\ReturnObject|array
  404. */
  405. public function getChapterList($book_id, $page_no = 1, $limit = 20)
  406. {
  407. if ((bool)Config::get('redis.change') == CacheConstants::BOOK_CHAPTER_CACHE_CHANGED_YES) {
  408. $cacheKey = CacheConstants::getBookChapterCacheKeyNew($book_id);
  409. $code = $this->getConnectCode($cacheKey);
  410. $redis = Redis::instanceBookChange($code);
  411. $list = $redis->zRange($cacheKey, 0, -1);
  412. } else {
  413. $redis = Redis::instanceBook();
  414. $cacheKey = CacheConstants::getBookChapterCacheKey($book_id);
  415. $list = $redis->lRange($cacheKey, 0, -1);
  416. }
  417. $all = []; //全部章节
  418. $data = [
  419. 'data' => [],
  420. 'pageNo' => $page_no,
  421. 'limit' => $limit,
  422. 'totalNum' => 0,
  423. 'totalPage' => 0,
  424. ];
  425. foreach ($list as $key => $value) {
  426. if ((bool)Config::get('redis.change') == CacheConstants::BOOK_CHAPTER_CACHE_CHANGED_YES) {
  427. $json = json_decode($value, true);
  428. } else {
  429. $json = json_decode(gzdecode($value), true);
  430. }
  431. $all[] = [
  432. 'id' => $json[4] ?? $json['chapterId'],
  433. 'idx' => count($all) + 1,
  434. 'name' => $json[5] ?? $json['chapterName'],
  435. ];
  436. }
  437. if ($limit > 0) {
  438. $data['data'] = array_slice($all, ($page_no - 1) * $limit, $limit);
  439. } else { //获取全部
  440. $data['data'] = $all;
  441. $data['limit'] = count($all);
  442. }
  443. if (!$data['data']) {
  444. LogService::notice('获取章节列表失败!书籍ID:' . $book_id . ' 分页:' . $page_no . ' 条数:' . $limit);
  445. return $this->setCode(ErrorCodeConstants::RESULT_EMPTY)->getReturn();
  446. }
  447. $data['totalNum'] = count($all);
  448. $data['totalPage'] = ceil(count($all) / $limit);
  449. return $this->setData($data)->getReturn();
  450. }
  451. /**
  452. * 获取章节详情
  453. *
  454. * @param int $book_id 书籍ID
  455. * @param int $chapter_id 章节ID
  456. * @return array | ReturnObject
  457. */
  458. public function getChapterInfo($book_id, $chapter_id, $user_id = false)
  459. {
  460. try{
  461. $chapterList = self::getChapterList($book_id, 1, -1);
  462. if ($chapterList->code != ErrorCodeConstants::SUCCESS) {
  463. LogService::notice('获取章节列表失败!书籍ID:' . $book_id);
  464. return $this->setCode(ErrorCodeConstants::RESULT_EMPTY)->getReturn();
  465. }
  466. $data = [];
  467. $allChapterIds = array_column($chapterList->data['data'], 'id');
  468. //如果章节不存在
  469. if (!in_array($chapter_id, $allChapterIds)) {
  470. $id = 0;
  471. foreach ($allChapterIds as $id) {
  472. if ($id >= $chapter_id) {
  473. break;
  474. }
  475. }
  476. $chapter_id = $id;
  477. }
  478. foreach ($chapterList->data['data'] as $key => $chapter) {
  479. if ($chapter['id'] >= $chapter_id) {
  480. $data['name'] = $chapter['name'];
  481. $data['idx'] = $chapter['idx'];
  482. $data['id'] = $chapter['id'];
  483. if ($key == 0) {
  484. $data['pre_id'] = '';
  485. } else {
  486. $data['pre_id'] = $chapterList->data['data'][$key - 1]['id'];
  487. }
  488. if (isset($chapterList->data['data'][$key + 1])) {
  489. $data['next_id'] = $chapterList->data['data'][$key + 1]['id'];
  490. } else {
  491. $data['next_id'] = '';
  492. }
  493. $chapter_id = $chapter['id'];
  494. break;
  495. }
  496. }
  497. $redis = Redis::instance();
  498. $cacheKey = CacheConstants::getBookSingleChapterCacheKey($chapter_id);
  499. $aChapter = [];
  500. if ($redis->exists($cacheKey)) {
  501. if(json_decode($redis->get($cacheKey), true)){
  502. $aChapter = json_decode($redis->get($cacheKey), true);
  503. }else{
  504. $aChapter = $redis->get($cacheKey);
  505. }
  506. } else {
  507. $chapter_result = $this->getChapter($book_id, $chapter_id)->data;
  508. $data['from'] = $chapter_result['from'];
  509. if (array_key_exists('name', $chapter_result)) {
  510. $data['name'] = $chapter_result['name'];
  511. }
  512. if ($content = $chapter_result['content']) {
  513. $content = str_replace("\r", '', $content);
  514. $arr = explode("\n", $content);
  515. foreach ($arr as $value) {
  516. $value = ltrim($value);
  517. $value = mb_ereg_replace('^( | )+','', $value);
  518. $value = mb_convert_encoding($value, "UTF-8", "UTF-8"); //强制转换一次UTF-8编码
  519. if ($value) {
  520. $aChapter[] = $value;
  521. }
  522. }
  523. } else {
  524. LogService::error('获取章节内容失败!ID:' . $book_id . ' 章节ID:' . $chapter_id);
  525. }
  526. if ($aChapter) {
  527. $keyNum = 'BCN:' . $chapter_id . ':' . date('i');
  528. if ($redis->incr($keyNum) > 10) {
  529. $redis->setex($cacheKey, 3600, json_encode($aChapter, true));
  530. }
  531. $redis->expire($keyNum, 60);
  532. }
  533. }
  534. $data['content'] = $aChapter;
  535. return $this->setData($data)->getReturn();
  536. }catch (\Exception $e){
  537. LogService::exception($e);
  538. return $this->setCode(ErrorCodeConstants::EXCEPTION)->getReturn();
  539. }
  540. }
  541. public function getChapter($book_id, $chapter_id)
  542. {
  543. $data = ['content'=>'','from'=>'cache'];
  544. $content = $this->getChapterEditedModel()->getChapterFromDb($chapter_id);
  545. if ($content) {
  546. $data = [
  547. 'content' => $content['content'],
  548. 'name' => $content['name'],
  549. 'from' => 'db',
  550. ];
  551. } else {
  552. $path = Config::get('site.chapter_path');
  553. if (empty($path)) {
  554. $path = 'cppartner';
  555. }
  556. $file = $this->getChapterContentPath($path, $book_id, $chapter_id);
  557. LogService::info('章节文件路径:' . $file);
  558. if (is_file($file)) {
  559. $content = file_get_contents($file);
  560. $data['content'] = $content;
  561. }
  562. }
  563. return $this->setData($data)->getReturn();
  564. }
  565. /**
  566. * 获取书籍章节路径
  567. * @param $path
  568. * @param $book_id
  569. * @param $chapter_id
  570. * @return string
  571. */
  572. public function getChapterContentPath($path, $book_id, $chapter_id)
  573. {
  574. return ROOT_PATH . "public/assets/" . $path . self::getPath($book_id) . "{$chapter_id}.txt";
  575. }
  576. /**
  577. * 获取书籍缓存连接参数
  578. * @param $val
  579. * @return int
  580. */
  581. public function getConnectCode($val)
  582. {
  583. $bKey = md5($val, true);
  584. $rv = (ord($bKey[3]) & 0xFF) << 24
  585. | (ord($bKey[2]) & 0xFF) << 16
  586. | (ord($bKey[1]) & 0xFF) << 8
  587. | ord($bKey[0]) & 0xFF;
  588. return $rv & 0xffffffff;
  589. }
  590. /**
  591. * 根据书籍id获取存储路径
  592. *
  593. * @param $book_id
  594. * @return string
  595. */
  596. protected function getPath($book_id)
  597. {
  598. $path = '/' . substr($book_id, 0, 1) . 'x' . substr($book_id, 1, 1)
  599. . '/' . substr($book_id, 0, 2) . 'x' . substr($book_id, 2, 1)
  600. . '/' . substr($book_id, 0, 3) . 'x' . substr($book_id, 3, 1)
  601. . '/' . $book_id . '/';
  602. return $path;
  603. }
  604. /**
  605. * @param int $book_id
  606. * @param int $referral_id
  607. * @return ReturnObject
  608. */
  609. public function getGuidChapterIdx($book_id = 0, $referral_id = 0)
  610. {
  611. try {
  612. //链接导粉
  613. if ($referral_id) {
  614. //检查内推外派,内推不导粉
  615. $ref_info = UrlService::instance()->getReferralModel()->getone($referral_id,false);
  616. if(isset($ref_info['push']) && $ref_info['push']){
  617. return $this->setData(0)->getReturn();
  618. }
  619. //推广链接导粉
  620. $idx = UrlService::instance()->getReferralModel()->getone($referral_id);
  621. if ($idx) {
  622. return $this->setData($idx)->getReturn();
  623. }
  624. }
  625. $runTime = UserService::instance()->getRunTimeObject();
  626. $admin_id = $runTime->adminId;
  627. if (empty($admin_id)) {
  628. //网站全局导粉章节数
  629. $web_guide = config('site.book_guide_chapter_idx');
  630. if ($web_guide) {
  631. return $this->setData($web_guide)->getReturn();
  632. } else {
  633. return $this->setData(0)->getReturn();
  634. }
  635. }
  636. $df_config = $runTime->adminConfig;
  637. $adminIds = [];
  638. if ($runTime->agentId) {
  639. $adminIds[] = $runTime->agentId;
  640. }
  641. $adminIds[] = $runTime->channelId;
  642. foreach ($adminIds as $adminId) {
  643. $guide = 0;
  644. //guide表导粉章节数 代理商导粉
  645. $key = 'GUIDE:' . $adminId;
  646. if (Redis::instance()->exists($key)) {
  647. $guide =(int)Redis::instance()->hget($key, $book_id);
  648. } else {
  649. $db_data = collection($this->getGuideModel()->field('book_id,chapter_idx')->where('admin_id=' . $adminId)->select())->toArray();
  650. if (empty($db_data)) {
  651. Redis::instance()->hset($key, 1, 1);
  652. } else {
  653. Redis::instance()->hdel($key, 1);
  654. $db_data_temp = [];
  655. foreach ($db_data as $value) {
  656. $db_data_temp[$value['book_id']] = $value['chapter_idx'];
  657. if ($value['book_id'] == $book_id) {
  658. $guide = $value['chapter_idx'];
  659. }
  660. }
  661. Redis::instance()->hMSet($key,$db_data_temp);
  662. unset($db_data_temp);
  663. unset($db_data);
  664. }
  665. Redis::instance()->expire($key, 86400);
  666. }
  667. if ($guide) {
  668. return $this->setData($guide)->getReturn();
  669. }
  670. }
  671. //渠道商全局导粉章节数
  672. if ($df_config['book_guide_chapter_idx']) {
  673. return $this->setData($df_config['book_guide_chapter_idx'])->getReturn();
  674. }
  675. //网站全局导粉章节数
  676. $web_guide = config('site.book_guide_chapter_idx');
  677. if ($web_guide) {
  678. return $this->setData($web_guide)->getReturn();
  679. }
  680. return $this->setData(0)->getReturn();
  681. } catch (\Exception $e) {
  682. LogService::exception($e);
  683. return $this->setData(0)->getReturn();
  684. }
  685. }
  686. /**
  687. * 随机获取导粉二维码
  688. * @param $admin_id
  689. * @return ReturnObject
  690. */
  691. public function getRandomGuideQrCode($admin_id)
  692. {
  693. $data = '';
  694. try {
  695. $redis = Redis::instance();
  696. $key = 'GW:' . $admin_id;
  697. if ($redis->exists($key)) {
  698. $gwRes = $redis->srandmember($key);
  699. if ($gwRes === '') {
  700. $data = '';
  701. } else {
  702. $data = json_decode($gwRes, true);
  703. }
  704. } else {
  705. $result = $this->getGuideWxModel()->where(['admin_id' => $admin_id, 'state' => '1'])->select();
  706. if ($result) {
  707. foreach ($result as $val) {
  708. $redis->sadd($key, json_encode($val, JSON_UNESCAPED_UNICODE));
  709. }
  710. $redis->expire($key, 86400 * 2);
  711. $gwRes = $redis->srandmember($key);
  712. $data = json_decode($gwRes, true);
  713. } else {
  714. $data = '';
  715. $redis->sadd($key, '');
  716. $redis->expire($key, 86400 * 2);
  717. }
  718. }
  719. } catch (\Exception $e) {
  720. LogService::exception($e);
  721. }
  722. return $this->setData($data)->getReturn();
  723. }
  724. /**
  725. * 设置最近阅读
  726. * @param $chapter_name 章节名称
  727. * @param $chapter_id 章节id
  728. * @param $bookId 书籍id
  729. * @param $chapter_idx 章节索引
  730. * @param null $userId 用户id
  731. * @return ReturnObject
  732. */
  733. public function setRecentlyRead($chapter_name, $chapter_id, $bookId, $chapter_idx, $userId = null)
  734. {
  735. try {
  736. if (empty($userId)) {
  737. $userId = UserService::instance()->getUserInfo()->id;
  738. }
  739. if (empty($bookId)) {
  740. return $this->getReturn();
  741. }
  742. $recentlyReadModel = $this->getUserRecentlyRead();
  743. $urKey = $recentlyReadModel->getURKey($userId);//最近阅读记录zset结构
  744. $ubKey = $recentlyReadModel->getUBKey($userId, $bookId);//最近阅读阅读记录每条的数据:hash结构
  745. $redis = Redis::instance();
  746. //用户阅读章节数,KL使用
  747. $cacheChapter = CacheConstants::getUserChapterCacheKey($userId);
  748. $redis->pfAdd($cacheChapter, $chapter_id);
  749. $RecentlyReadDB = $this->getUserRecentlyRead()->setConnect($userId);
  750. if (empty($userId)) { //游客
  751. $chapterInfo = [
  752. 'id' => $chapter_id,
  753. 'name' => $chapter_name,
  754. ];
  755. $RecentlyReadDB->recentCookie($bookId, $chapterInfo); //阅读记录记入cookie 只记录5条数据
  756. return $this->getReturn();
  757. }
  758. if (!$redis->exists($urKey)) { //如果redis里最近阅读记录为空拉取一下最近阅读记录(防灾)
  759. $RecentlyReadDB->getRecentlyRead(0, 10, $userId);
  760. }
  761. if (!($userRead = $redis->hGetAll($ubKey))) {
  762. $userRead = $RecentlyReadDB->getone($userId, $bookId);
  763. $userRead = $userRead ? $userRead->toArray() : null;
  764. }
  765. $userRecentInfo = [
  766. 'chapter_id' => $chapter_id,
  767. 'chapter_name' => $chapter_name,
  768. 'updatetime' => time(),
  769. 'flag' => 1
  770. ];
  771. if (config('site.book_auto_shelf_chapter_num') <= $chapter_idx) {
  772. $userRecentInfo['book_shelf_add'] = 1;
  773. $userRecentInfo['book_shelf_flag'] = 1;
  774. $usKey = $recentlyReadModel->getUSKey($userId);
  775. if (!$redis->exists($usKey)) {
  776. $this->_getBookShelf(0, 10, $userId);
  777. }
  778. $redis->zAdd($usKey, $userRecentInfo['updatetime'], $ubKey);
  779. $redis->expire($usKey, 43200);
  780. $this->delRecommendShelfBookByBookId($bookId, $userId);
  781. }
  782. if (!empty($userRead)) { //更新
  783. $getId = $userRead['id'];
  784. $RecentlyReadDB->where(['id' => $getId])->update($userRecentInfo);
  785. $userRecentInfo = array_merge($userRead, $userRecentInfo);//构造redis用的数据
  786. } else {//创建阅读记录
  787. $userRecentInfo['user_id'] = $userId;
  788. $userRecentInfo['book_id'] = $bookId;
  789. $userRecentInfo['createtime'] = time();
  790. $RecentlyReadDB->insert($userRecentInfo);
  791. $id = $RecentlyReadDB->getLastInsID();
  792. $userRecentInfo['id'] = $id;
  793. if (!$redis->exists($urKey)) { //(如果redis里最近阅读记录为空拉取一下最近阅读记录防灾)
  794. $RecentlyReadDB->getRecentlyRead(time() + 1, 10, $userId);
  795. }
  796. }
  797. $redis->hmset($ubKey, $userRecentInfo);
  798. $redis->expire($ubKey, 43200); //设置失效时间
  799. $redis->zadd($urKey, $userRecentInfo['updatetime'], $ubKey);
  800. $redis->expire($urKey, 43200); //设置失效时间
  801. return $this->getReturn();
  802. } catch (\Exception $e) {
  803. return $this->getExceptionReturn($e);
  804. }
  805. }
  806. /**
  807. * 获取章节阅读配置
  808. */
  809. public function getReadingSetting()
  810. {
  811. $return = [
  812. 'read_body_fontsize' => 'read_body_fontsize_2',
  813. 'read_theme' => 'read_theme_1',
  814. 'day_night' => '',
  815. ];
  816. if (!Config::get('template.view_theme')) {
  817. $return['read_theme'] = 'read_theme_2'; //西瓜
  818. } else {
  819. $return['read_theme'] = 'read_theme_1'; //袋鼠 沙发 美书
  820. }
  821. if (Cookie::has('cs')) {
  822. $cs['l'] = 1;
  823. $cs = json_decode(Cookie::get('cs'), true); //章节排序 l=1正序 l=2倒序
  824. if ($cs['f'] !== null) {
  825. //字号
  826. $return['read_body_fontsize'] = 'read_body_fontsize_' . intval($cs['f']);
  827. }
  828. if ($cs['b'] !== null) {
  829. //背景色
  830. $return['read_theme'] = 'read_theme_' . intval($cs['b']);
  831. }
  832. if ($cs['d'] == 2) {
  833. //白天/夜间 主题
  834. $return['day_night'] = 'read_theme_5';
  835. }
  836. }
  837. return $this->setData($return)->getReturn();
  838. }
  839. /**
  840. * 获取书籍默认链接
  841. * @param $book_id
  842. * @return ReturnObject
  843. */
  844. public function getBookIndexPage($book_id)
  845. {
  846. return $this->setData('/index/book/info?book_id=' . $book_id)->getReturn();
  847. }
  848. /**
  849. * 随机获取一本返回推荐书籍
  850. * @return ReturnObject
  851. */
  852. public function getReturnRecommandBook()
  853. {
  854. $book_data = false;
  855. $recentNum = Env::get('app.return_filter_recent_num', 100);
  856. $recentRead = UserService::instance()->getUserRecentlyReadModel()->getRecentlyRead(0, $recentNum);
  857. $book_ids = [];
  858. if ($recentRead['totalNum'] > 0) {
  859. $book_ids = array_column($recentRead['data'], 'book_id');
  860. }
  861. $count = $this->getReturnRecommandModel()
  862. ->where('status', 'normal')
  863. ->whereNotIn('book_id', $book_ids)
  864. ->count();
  865. if ($count) {
  866. $index = rand(0, $count - 1);
  867. $book_list = model('ReturnRecommand')
  868. ->where('status', 'normal')
  869. ->whereNotIn('book_id', $book_ids)
  870. ->limit($index, 1)
  871. ->select();
  872. $book = $book_list[0];
  873. $book_data = BookService::instance()->getBookModel()->BookInfo($book['book_id']);
  874. if ($book_data) {
  875. $book_data['url'] = '/index/book/chapter?book_id=' . $book['book_id'] . '&sid=' . $book_data['first_chapter_id'];
  876. }
  877. }
  878. return $this->setData($book_data)->getReturn();
  879. }
  880. /**
  881. * @param $bookInfo 书籍信息
  882. * @param $chapterId 章节id
  883. * @param $type 类型,0当前章节;1上一章;2、下一章;
  884. * @return ReturnObject
  885. */
  886. public function getBookChapterInfoForClient($bookInfo, $chapterId, $type)
  887. {
  888. try {
  889. $bookId = $bookInfo['id'];
  890. //如果章节id为空,则获取当前书籍第一章的内容
  891. if (empty($chapterId)) {
  892. $chapterId = $bookInfo['first_chapter_id'];
  893. }
  894. if (empty($chapterId)) {
  895. return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg('首章id不存在')->getReturn();
  896. }
  897. $chapterResult = $this->getChapterInfoForClient($bookId, $chapterId, $type);
  898. if ($chapterResult->code != ErrorCodeConstants::SUCCESS) {
  899. return $this->setCode($chapterResult->code)->setMsg($chapterResult->msg)->getReturn();
  900. }
  901. return $this->setData($chapterResult->data)->getReturn();
  902. } catch (\Exception $e) {
  903. LogService::exception($e);
  904. $this->setCode(ErrorCodeConstants::EXCEPTION)->getReturn();
  905. }
  906. }
  907. /**
  908. * 获取章节信息
  909. * @param $bookId 书籍id
  910. * @param $chapterId 章节id
  911. * @param $type 类型,0当前章节;1上一章;2、下一章;
  912. * @return ReturnObject
  913. */
  914. private function getChapterInfoForClient($bookId, $chapterId, $type)
  915. {
  916. try {
  917. $chapterList = self::getChapterList($bookId, 1, -1);
  918. if ($chapterList->code != ErrorCodeConstants::SUCCESS) {
  919. LogService::notice('获取章节列表失败!书籍ID:' . $bookId);
  920. return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg('章节不存在')->getReturn();
  921. }
  922. $data = [];
  923. foreach ($chapterList->data['data'] as $key => $chapter) {
  924. if ($chapter['id'] == $chapterId) {
  925. $idx = $key;
  926. switch ($type) {
  927. case ClientApiConstants::GET_CHAPTER_TYPE_PRE:
  928. $idx = $key - 1;
  929. break;
  930. case ClientApiConstants::GET_CHAPTER_TYPE_NEXT:
  931. $idx = $key + 1;
  932. break;
  933. default:
  934. break;
  935. }
  936. $chapterInfo = isset($chapterList->data['data'][$idx]) ? $chapterList->data['data'][$idx] : null;
  937. if (isset($chapterInfo)) {
  938. $chapterInfoId = $chapterInfo['id'];
  939. $data['name'] = $chapterInfo['name'];
  940. $data['idx'] = $chapterInfo['idx'];
  941. $data['id'] = $chapterInfoId;
  942. if ($idx == 0) {
  943. $data['pre_id'] = '';
  944. } else {
  945. $data['pre_id'] = $chapterList->data['data'][$idx - 1]['id'];
  946. }
  947. if (isset($chapterList->data['data'][$idx + 1])) {
  948. $data['next_id'] = $chapterList->data['data'][$idx + 1]['id'];
  949. } else {
  950. $data['next_id'] = '';
  951. }
  952. }
  953. break;
  954. }
  955. }
  956. if (empty($data)) {
  957. return $this->setCode(ClientApiConstants::CHAPTER_NOT_EXIST)->getReturn();
  958. }
  959. $redis = Redis::instance();
  960. $cacheKey = CacheConstants::getBookSingleChapterCacheKey($chapterInfoId);
  961. $aChapter = [];
  962. if ($redis->exists($cacheKey)) {
  963. if (json_decode($redis->get($cacheKey), true)) {
  964. $aChapter = json_decode($redis->get($cacheKey), true);
  965. } else {
  966. $aChapter = $redis->get($cacheKey);
  967. $aChapter = str_replace('<p>', '', $aChapter);
  968. $aChapter = explode('</p>', $aChapter);
  969. }
  970. } else {
  971. $path = Config::get('site.chapter_path');
  972. if (empty($path)) {
  973. $path = 'cppartner';
  974. }
  975. $file = $this->getChapterContentPath($path, $bookId, $chapterInfoId);
  976. LogService::info('章节文件路径:' . $file);
  977. if (is_file($file)) {
  978. $content = file_get_contents($file);
  979. $content = str_replace("\r", '', $content);
  980. $arr = explode("\n", $content);
  981. foreach ($arr as $value) {
  982. $value = ltrim($value, "\0\t\n\\x0B\r  ");
  983. $value = mb_convert_encoding($value, "UTF-8", "UTF-8"); //强制转换一次UTF-8编码
  984. if ($value) {
  985. $aChapter[] = $value;
  986. }
  987. }
  988. } else {
  989. LogService::error('获取章节内容失败!书籍路径:' . $file . ',ID:' . $bookId . ' 章节ID:' . $chapterInfoId);
  990. $data['status'] = ClientApiConstants::ORDER_CHAPTER_STATE_NO_CONTENT;
  991. }
  992. if ($aChapter) {
  993. $keyNum = sprintf('BCN:%s:%s', $chapterInfoId, date('i'));
  994. if ($redis->incr($keyNum) > 10) {
  995. $redis->setex($cacheKey, 3600, json_encode($aChapter, true));
  996. }
  997. $redis->expire($keyNum, 60);
  998. }
  999. }
  1000. $data['content'] = is_array($aChapter) ? implode("\n", $aChapter) : $aChapter;
  1001. return $this->setData($data)->getReturn();
  1002. } catch (\Exception $e) {
  1003. LogService::exception($e);
  1004. return $this->getExceptionReturn($e);
  1005. }
  1006. }
  1007. /**
  1008. * 获取书籍全部章节信息
  1009. * @param $bookId 书籍id
  1010. * @return ReturnObject
  1011. */
  1012. public function getChapterListForClient($bookId)
  1013. {
  1014. $chapters = model("Book")::getChapterLimit($bookId, 0, -1);
  1015. if (empty($chapters)) {
  1016. return $this->setCode(ErrorCodeConstants::RESULT_EMPTY)->setMsg('没有获取到目录信息')->getReturn();
  1017. } else {
  1018. return $this->setData($chapters)->getReturn();
  1019. }
  1020. }
  1021. /**
  1022. * 获取app使用的推荐书籍id列表
  1023. * @param $sex 性别
  1024. * @param int $limit
  1025. * @return array|mixed
  1026. */
  1027. public function getRecommendBookIdsForClient($sex, $limit)
  1028. {
  1029. $redis = Redis::instance();
  1030. $key = sprintf('%s:%s:%s', ClientApiConstants::RECOMMAND_BOOK_KEY, $sex, $limit);
  1031. if ($redis->exists($key)) {
  1032. $data = $redis->get($key);
  1033. $ids = json_decode($data, true);
  1034. } else {
  1035. if ($sex != 0) {
  1036. $map['sex'] = $sex;
  1037. }
  1038. $map['state'] = 1;
  1039. $ids = model('Book')->where($map)->order("idx", "desc")->limit($limit)->column("id");
  1040. $json = json_encode($ids);
  1041. $redis->setex($key, 900, $json);
  1042. }
  1043. return $ids;
  1044. }
  1045. /**
  1046. * 删除最近阅读记录
  1047. * @param $recentId
  1048. * @param $bookId
  1049. * @param null $userId
  1050. * @return ReturnObject
  1051. */
  1052. public function removeRecentlyRead_old($recentId, $bookId, $userId = null)
  1053. {
  1054. $result = $this->getUserRecentlyRead()->removeBook($recentId, $bookId, $userId);
  1055. if ($result) {
  1056. return $this->getReturn();
  1057. } else {
  1058. return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除最近阅读失败')->getReturn();
  1059. }
  1060. }
  1061. /**
  1062. * @param $aRecentIds
  1063. * @param $aBookIds
  1064. * @param null $userId
  1065. * @return ReturnObject
  1066. */
  1067. public function removeRecentlyRead($aRecentIds, $aBookIds, $userId = null)
  1068. {
  1069. if (!$userId) {
  1070. $userId = UserService::instance()->getUserInfo()->id;
  1071. }
  1072. if (empty($aRecentIds)) {
  1073. LogService::error('删除最近阅读失败,$aRecentIds为空,userId:' . $userId);
  1074. return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除最近阅读失败')->getReturn();
  1075. }
  1076. if (empty($aBookIds)) {
  1077. LogService::error('删除最近阅读失败,$aBookIds为空,userId:' . $userId);
  1078. return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除最近阅读失败')->getReturn();
  1079. }
  1080. $userRecentModel = $this->getUserRecentlyRead()->setConnect($userId);
  1081. $dbRes = $userRecentModel->whereIn('id', $aRecentIds)->update(['flag' => 0]);
  1082. if ($dbRes) {
  1083. $redis = Redis::instance();
  1084. $urKey = $userRecentModel->getURKey($userId);
  1085. $ubKeys = [];
  1086. foreach ($aBookIds as $bookId) {
  1087. $ubKeys[] = $userRecentModel->getUBKey($userId, $bookId);
  1088. }
  1089. $redis->zrem($urKey, ...$ubKeys);
  1090. $getUBKeyRedisIndex = Redis::splitKeysByRule($ubKeys);
  1091. array_walk($getUBKeyRedisIndex, function ($v, $k) {
  1092. $redis = Redis::getRedisConnect($k);
  1093. $redis->del(...$v);
  1094. });
  1095. }
  1096. return $this->getReturn();
  1097. }
  1098. /**
  1099. * 获取书籍客户端需要的状态
  1100. * @param array $bookInfo 书籍信息
  1101. * @param $chapterKey 最后一章id
  1102. * @return int
  1103. */
  1104. public static function makeBookStatusForClient($bookInfo, $chapterKey = null)
  1105. {
  1106. if ($bookInfo['state'] == 0) {
  1107. return ClientApiConstants::BOOK_STATE_OFF;
  1108. }
  1109. if (isset($chapterKey) && $bookInfo['last_chapter_id'] != $chapterKey) {
  1110. return ClientApiConstants::BOOK_STATE_UPDATE;
  1111. }
  1112. $now = time();
  1113. if ($now > $bookInfo['free_stime'] && $now < $bookInfo['free_etime']) {
  1114. return ClientApiConstants::BOOK_STATE_LIMIT_FREE;
  1115. }
  1116. return ClientApiConstants::BOOK_STATE_NORMAL;
  1117. }
  1118. #region 书架相关
  1119. /**
  1120. * 从最近阅读记录表获取书架信息
  1121. * @param int $updatetime
  1122. * @param int $pageSize
  1123. * @param bool $userId
  1124. * @return array
  1125. */
  1126. private function _getBookShelf($updatetime = 0, $pageSize = 10, $userId = false)
  1127. {
  1128. if (!$userId) {
  1129. $userId = UserService::instance()->getUserInfo()->id;
  1130. }
  1131. $redis = Redis::instance();
  1132. $usKey = $this->getUserRecentlyRead()->getUSKey($userId);
  1133. if ($updatetime) {
  1134. $pageBegin = $updatetime - 1;
  1135. $resUS = $redis->zRevRangeByScore($usKey, $pageBegin, '-inf', ['limit' => [0, $pageSize]]);
  1136. } else {
  1137. $pageBegin = 0;
  1138. $resUS = $redis->zRevRange($usKey, 0, $pageSize - 1);
  1139. }
  1140. if (empty($resUS)) {
  1141. $result = $this->getUserRecentlyRead()->getShelfRecentlyByDB($userId, $pageBegin, $pageSize);
  1142. } else {
  1143. $result = $this->getUserRecentlyRead()->getShelfRecentlyByRedis($userId, $resUS);
  1144. #region 如果U-B中没有book_shelf_add或者book_shelf_flag属性,那么从数据库中获取
  1145. // 此处代码上线一段时间后可删除
  1146. foreach ($result as $item) {
  1147. if (!isset($item['book_shelf_add']) || !isset($item['book_shelf_flag'])) {
  1148. // 将shelf库里的数据刷到阅读记录
  1149. $result = $this->getUserRecentlyRead()->getShelfRecentlyByDB($userId, $pageBegin, $pageSize);
  1150. break;
  1151. }
  1152. }
  1153. #endregion
  1154. }
  1155. $bookIds = array_column($result, 'book_id');
  1156. $booksInfo = model('Book')->getBooksInfo($bookIds);
  1157. foreach ($result as &$item) {
  1158. $bookId = $item['book_id'];
  1159. if (isset($booksInfo[$bookId])) {
  1160. $book = $booksInfo[$bookId];
  1161. $item['image'] = $book['image'];
  1162. $item['name'] = $book['name'];
  1163. $item['state'] = $book['state'];
  1164. $item['free_stime'] = $book['free_stime'];
  1165. $item['free_etime'] = $book['free_etime'];
  1166. $item['chapter_id'] = empty($item['chapter_id']) ? $book['first_chapter_id'] : $item['chapter_id'];
  1167. $item['last_chapter_id'] = $book['last_chapter_id'];
  1168. $item['recommend'] = false;
  1169. }
  1170. }
  1171. return $result;
  1172. }
  1173. /**
  1174. * 获取用户的书架
  1175. * @return ReturnObject
  1176. */
  1177. public function getBookShelf()
  1178. {
  1179. try {
  1180. // 获取用户加入书架的书籍id
  1181. $shelfBooks = $this->getRecommendShelfBooks();
  1182. $recommendShelfBooks = ArrayHelper::array_column_search($shelfBooks, 'insert_type',
  1183. BookConstants::SHELF_INSERT_TYPE_RECOMMEND, false);
  1184. $recommendShelfBookIds = array_column($recommendShelfBooks, 'book_id');
  1185. $userInfo = UserService::instance()->getUserInfo();
  1186. $userId = $userInfo->id;
  1187. $redis = Redis::instance();
  1188. #region 如果shelf数据库有用户主动加入的书籍,迁移到最近阅读表,并删除shelf表的数据
  1189. if (count($shelfBooks) > count($recommendShelfBooks)) {
  1190. $userRecentlyModel = $this->getUserRecentlyRead()->setConnect($userId);
  1191. $shelfBookIds = array_column($shelfBooks, 'book_id');
  1192. $needToRecentBookIds = array_diff($shelfBookIds, $recommendShelfBookIds);
  1193. //用户已经阅读过的书籍
  1194. $existBookIds = $userRecentlyModel->whereIn('book_id',
  1195. $needToRecentBookIds)->where('user_id', $userId)->column('book_id');
  1196. //修改数据库
  1197. if (!empty($existBookIds)) {
  1198. $userRecentlyModel->whereIn('book_id',
  1199. $existBookIds)->where('user_id', $userId)->update([
  1200. 'book_shelf_add' => 1,
  1201. 'book_shelf_flag' => 1
  1202. ]);
  1203. }
  1204. //插入数据库
  1205. $unReadBookIds = array_diff($needToRecentBookIds, $existBookIds);
  1206. $insData = [];
  1207. foreach ($unReadBookIds as $unReadBookId) {
  1208. $insData[] = [
  1209. 'user_id' => $userId,
  1210. 'book_id' => $unReadBookId,
  1211. 'chapter_id' => 0,
  1212. 'chapter_name' => '',
  1213. 'flag' => 1,
  1214. 'book_shelf_add' => 1,
  1215. 'book_shelf_flag' => 1,
  1216. 'createtime' => time(),
  1217. 'updatetime' => time(),
  1218. ];
  1219. }
  1220. $userRecentlyModel->insertAll($insData);
  1221. #region 删除redis U-R U-B
  1222. $redis->del($userRecentlyModel->getURKey($userId));
  1223. $ubKeys = [];
  1224. foreach ($needToRecentBookIds as $needToRecentBookId) {
  1225. $ubKeys[] = $userRecentlyModel->getUBKey($userId, $needToRecentBookId);
  1226. }
  1227. $getUBRedisIndex = Redis::splitKeysByRule($ubKeys);
  1228. array_walk($getUBRedisIndex, function ($v, $k) {
  1229. $redis = Redis::getRedisConnect($k);
  1230. $redis->del(...$v);
  1231. });
  1232. $redis->del($this->_shelfBooksKey($userId));
  1233. #endregion
  1234. //删除书架数据库里的非推荐书籍
  1235. $this->getBookShelfModel()->setConnect($userId)->where('user_id', $userId)->whereIn('book_id',
  1236. $needToRecentBookIds)->update(['flag' => Common::NO]);
  1237. }
  1238. #endregion
  1239. $userRecentlyList = model('UserRecentlyRead')->setConnect($userId)->getRecentlyRead(0, 50, $userId);
  1240. $userRecentlyList = $userRecentlyList['data'];
  1241. $channelId = UserService::instance()->getUserChannelId()->data;
  1242. $isWater = WaterBookService::instance()->showBook($channelId, $userId, Ip::ip());
  1243. #region 为推荐书籍增加书籍属性
  1244. $recommendShelfBookInfos = $this->getBookModel()->getBooksInfo($recommendShelfBookIds);
  1245. $recommendSimpleBooksInfo = [];
  1246. foreach ($recommendShelfBookInfos as $recommendShelfBookInfo) {
  1247. $userRecentlyItem = ArrayHelper::array_column_search($userRecentlyList, 'book_id',
  1248. $recommendShelfBookInfo['id']);
  1249. $recentlyChapterId = empty($userRecentlyItem) ? $recommendShelfBookInfo['first_chapter_id'] : $userRecentlyItem['chapter_id'];
  1250. $_recommendBookInfo = ArrayHelper::array_column_search($recommendShelfBooks, 'book_id',
  1251. $recommendShelfBookInfo['id']);
  1252. $recommendBookUpdatetime = empty($_recommendBookInfo['updatetime']) ? 0 : $_recommendBookInfo['updatetime'];
  1253. $item = [
  1254. 'book_id' => $recommendShelfBookInfo['id'],
  1255. 'image' => $recommendShelfBookInfo['image'],
  1256. 'name' => $recommendShelfBookInfo['name'],
  1257. 'state' => $recommendShelfBookInfo['state'],
  1258. 'free_stime' => $recommendShelfBookInfo['free_stime'],
  1259. 'free_etime' => $recommendShelfBookInfo['free_etime'],
  1260. 'chapter_id' => $recentlyChapterId,
  1261. 'last_chapter_id' => $recommendShelfBookInfo['last_chapter_id'],
  1262. 'updatetime' => $recommendBookUpdatetime,
  1263. 'recommend' => true,
  1264. ];
  1265. if ($isWater && (!isset($recommendShelfBookInfo['classify_white_list']) || $recommendShelfBookInfo['classify_white_list'] != 1)) {
  1266. $item['state'] = 0;
  1267. }
  1268. $recommendSimpleBooksInfo[] = $item;
  1269. }
  1270. #endregion
  1271. // $recentSimpleList = $this->_getBookShelf(0, 100);
  1272. #region 从最近阅读记录表获取书架信息
  1273. $usKey = $this->getUserRecentlyRead()->getUSKey($userId);
  1274. $pageBegin = 0;
  1275. $resUS = $redis->zRevRange($usKey, 0, 99);
  1276. if (empty($resUS)) {
  1277. $result = $this->getUserRecentlyRead()->getShelfRecentlyByDB($userId, $pageBegin, 100);
  1278. } else {
  1279. $result = $this->getUserRecentlyRead()->getShelfRecentlyByRedis($userId, $resUS);
  1280. }
  1281. $bookIds = array_column($result, 'book_id');
  1282. $booksInfo = model('Book')->getBooksInfo($bookIds);
  1283. foreach ($result as &$item) {
  1284. $bookId = $item['book_id'];
  1285. if (isset($booksInfo[$bookId])) {
  1286. $book = $booksInfo[$bookId];
  1287. $item['image'] = $book['image'];
  1288. $item['name'] = $book['name'];
  1289. $item['state'] = $book['state'];
  1290. if ($isWater && (!isset($book['classify_white_list']) || $book['classify_white_list'] != 1)) {
  1291. $item['state'] = 0;
  1292. }
  1293. $item['free_stime'] = $book['free_stime'];
  1294. $item['free_etime'] = $book['free_etime'];
  1295. $item['chapter_id'] = empty($item['chapter_id']) ? $book['first_chapter_id'] : $item['chapter_id'];
  1296. $item['last_chapter_id'] = $book['last_chapter_id'];
  1297. $item['recommend'] = false;
  1298. }
  1299. }
  1300. $recentSimpleList = $result;
  1301. #endregion
  1302. $booksInfo = array_merge($recommendSimpleBooksInfo, $recentSimpleList);
  1303. $sortArr = array_column($booksInfo, 'updatetime');
  1304. array_multisort($sortArr, SORT_DESC, $booksInfo);
  1305. #region 书架书籍展示逻辑
  1306. $freeLimit = array_keys(BuyMoreService::instance()->getFreeLimitCacheBook($userId)->data);
  1307. $permanent = FinancialService::instance()->getConsumeModel()->setConnect($userId)
  1308. ->where('type', BookConstants::BOOK_BILLING_MODEL_BOOK)
  1309. ->where('kandian', 0)
  1310. ->where('free_kandian', 0)
  1311. ->column('book_id');
  1312. foreach ($booksInfo as &$item) {
  1313. if ($item['state'] == 0) {//书籍下架
  1314. $item['show_status'] = 0;
  1315. } elseif (in_array($item['book_id'], $permanent)) {//充值加购,赠送
  1316. $item['show_status'] = 1;
  1317. } elseif ($item['free_stime'] <= time() && $item['free_etime'] >= time()) {//限免
  1318. $item['show_status'] = 2;
  1319. } elseif (in_array($item['book_id'], $freeLimit)) {//充值加购,限免
  1320. $item['show_status'] = 5;
  1321. } elseif ($userInfo->vip_endtime > time() && !VipCpService::instance()->isFreeCpBookForVip($item['book_id'], $userId, $userInfo->vip_endtime)) {
  1322. // vip 用户进行 CP书籍,对VIP收费
  1323. $item['show_status'] = 11;
  1324. } elseif ($item['recommend'] == true) {//推荐
  1325. $item['show_status'] = 3;
  1326. } elseif ($item['chapter_id'] < $item['last_chapter_id']) {//更新
  1327. $item['show_status'] = 4;
  1328. } elseif (in_array($item['book_id'], $freeLimit)) {//充值加购,限免
  1329. $item['show_status'] = 5;
  1330. } else {
  1331. $item['show_status'] = 10;
  1332. }
  1333. }
  1334. #endregion
  1335. return $this->setCode(ErrorCodeConstants::SUCCESS)->setData($booksInfo)->getReturn();
  1336. } catch (\Exception $e) {
  1337. return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg($e->getMessage())->getReturn();
  1338. }
  1339. }
  1340. /**
  1341. * 是否展示 '非VIP书籍' 角标
  1342. * @param $book_id
  1343. * @return bool
  1344. */
  1345. public function showCpVipSubScript($book_id)
  1346. {
  1347. return false;
  1348. /*$userInfo = UserService::instance()->getUserInfo();
  1349. $vip_endtime = $userInfo->vip_endtime;
  1350. if ($vip_endtime > time()) {
  1351. if (VipCpService::instance()->isFreeCpBookForVip($book_id, $userInfo->id, $vip_endtime)) {
  1352. return false;
  1353. } else {
  1354. return true;
  1355. }
  1356. } else {
  1357. return false;
  1358. }*/
  1359. }
  1360. /**
  1361. * 获取书籍角标, 角标优先级:下架 -> 限免 -> 付费书籍 -> 推荐
  1362. * @param array $bookinfo
  1363. * @return int
  1364. */
  1365. /*public function getBookSubscript(array $bookinfo)
  1366. {
  1367. $show_status = -1;
  1368. $userInfo = UserService::instance()->getUserInfo();
  1369. $userId = $userInfo->id;
  1370. $vip_endtime = $userInfo->vip_endtime;
  1371. $bookinfo['recommend'] = true;
  1372. $freeLimit = array_keys(BuyMoreService::instance()->getFreeLimitCacheBook($userId)->data);
  1373. $permanent = FinancialService::instance()->getConsumeModel()->setConnect($userId)
  1374. ->where('type', BookConstants::BOOK_BILLING_MODEL_BOOK)
  1375. ->where('kandian', 0)
  1376. ->where('free_kandian', 0)
  1377. ->column('book_id');
  1378. if ($bookinfo['state'] == 0) {//书籍下架
  1379. $show_status = 0;
  1380. } elseif (in_array($bookinfo['id'], $permanent)) {//充值加购,赠送
  1381. $show_status = 1;
  1382. } elseif ($bookinfo['free_stime'] <= time() && $bookinfo['free_etime'] >= time()) {//限免
  1383. $show_status = 2;
  1384. } elseif (in_array($bookinfo['id'], $freeLimit)) {//充值加购,限免
  1385. $show_status = 5;
  1386. } elseif ($vip_endtime > time()) { // vip 用户进行
  1387. if (!VipCpService::instance()->isFreeCpBookForVip($bookinfo['id'], $userId, $vip_endtime)) {
  1388. // CP书籍,对VIP收费
  1389. $show_status = 11;
  1390. }
  1391. } elseif ($bookinfo['recommend'] == true) {//推荐
  1392. $show_status = 3;
  1393. } elseif ($bookinfo['chapter_id'] < $bookinfo['last_chapter_id']) {//更新
  1394. $show_status = 4;
  1395. } elseif (in_array($bookinfo['id'], $freeLimit)) {//充值加购,限免
  1396. $show_status = 5;
  1397. } else {
  1398. $show_status = 10;
  1399. }
  1400. return $show_status;
  1401. }*/
  1402. /**
  1403. * 获取用户加入书架书籍信息列表
  1404. * @param $userId
  1405. * @return array|mixed
  1406. */
  1407. private function getRecommendShelfBooks($userId = null)
  1408. {
  1409. if (empty($userId)) {
  1410. $userInfo = UserService::instance()->getUserInfo();
  1411. $userId = $userInfo->id;
  1412. }
  1413. $key = $this->_shelfBooksKey($userId);
  1414. $redis = Redis::instance();
  1415. if ($strData = $redis->get($key)) {
  1416. $shelfBooks = json_decode($strData, true);
  1417. } else {
  1418. $shelfBooks = $this->getBookShelfModel()->setConnect($userId)
  1419. ->where('user_id', $userId)
  1420. ->where('flag', Common::YES)
  1421. ->order('id')
  1422. ->field(['id', 'book_id', 'updatetime', 'insert_type'])
  1423. ->select();
  1424. $strData = json_encode($shelfBooks);
  1425. $redis->setex($key, 86400, $strData);
  1426. }
  1427. return $shelfBooks;
  1428. }
  1429. /**
  1430. * 删除用户推荐库中的推荐书籍
  1431. * @param $bookId
  1432. * @param $userId
  1433. */
  1434. private function delRecommendShelfBookByBookId($bookId, $userId)
  1435. {
  1436. $recommendBooks = $this->getRecommendShelfBooks($userId);
  1437. $recmdShelfBookInfo = ArrayHelper::array_column_search($recommendBooks, 'book_id', $bookId);
  1438. if (!empty($recmdShelfBookInfo)) {
  1439. $key = $this->_shelfBooksKey($userId);
  1440. $redis = Redis::instance();
  1441. $this->getBookShelfModel()->setConnect($userId)
  1442. ->where('id', $recmdShelfBookInfo['id'])
  1443. ->update(['flag' => Common::NO]);
  1444. $recommendBooksDeleted = [];
  1445. foreach ($recommendBooks as $item) {
  1446. if ($item['id'] == $recmdShelfBookInfo['id']) {
  1447. continue;
  1448. }
  1449. $recommendBooksDeleted[] = $item;
  1450. }
  1451. $strData = json_encode($recommendBooksDeleted);
  1452. $redis->setex($key, 86400, $strData);
  1453. }
  1454. }
  1455. /**
  1456. * 获取书架展示书库的书籍id列表
  1457. * @return array|mixed
  1458. */
  1459. public function getShelfRecommendBookIds($sex,$isWater=false)
  1460. {
  1461. $sex = $sex == 0 ? 1 : $sex;
  1462. $key = $this->_shelfRecommendBookIdsKey($sex,$isWater);
  1463. $redis = Redis::instance();
  1464. if ($strData = $redis->get($key)) {
  1465. $ids = json_decode($strData, true);
  1466. } else {
  1467. $waterWhere = $isWater ? ' and classify_white_list=1' : '';
  1468. $obj = model('BookshelfRecommand')->alias('bsf');
  1469. $obj->join('book','book.id=book_id and book.state=1'.$waterWhere,'inner');
  1470. $ids = $obj ->where('bsf.sex', $sex)->where('bsf.status', Common::STATUS_NORMAL)->limit(6)->column('bsf.book_id');
  1471. $strData = json_encode($ids);
  1472. $redis->set($key, $strData);
  1473. $redis->expire($key,900);
  1474. }
  1475. return $ids;
  1476. }
  1477. /**
  1478. * 书籍推广书籍插入用户书架
  1479. * @param $userId
  1480. * @param $sex
  1481. * @return bool
  1482. */
  1483. public function insertShelfRecommendBooks($userId, $sex,$channelId)
  1484. {
  1485. try {
  1486. $channelId = AdminService::instance()->getAdminExtendModel()->getChannelId($channelId);
  1487. $isWater = WaterBookService::instance()->showBook($channelId,$userId);
  1488. $bookIds = $this->getShelfRecommendBookIds($sex, $isWater);
  1489. if (empty($bookIds)) {
  1490. return false;
  1491. }
  1492. $data = [];
  1493. foreach ($bookIds as $bookId) {
  1494. $data[] = [
  1495. 'user_id' => $userId,
  1496. 'book_id' => $bookId,
  1497. 'insert_type' => BookConstants::SHELF_INSERT_TYPE_RECOMMEND,
  1498. 'createtime' => time(),
  1499. 'updatetime' => time(),
  1500. ];
  1501. }
  1502. $this->getBookShelfModel()->setConnect($userId)->insertAll($data);
  1503. return true;
  1504. } catch (\Exception $e) {
  1505. LogService::error($e->getMessage());
  1506. }
  1507. }
  1508. /**
  1509. * 清除书架展示书库缓存
  1510. */
  1511. public function clearShelfRecommendBookIds()
  1512. {
  1513. try {
  1514. $redis = Redis::instance();
  1515. $key = $this->_shelfRecommendBookIdsKey(1);
  1516. $redis->del($key);
  1517. $key = $this->_shelfRecommendBookIdsKey(2);
  1518. $redis->del($key);
  1519. $key = $this->_shelfRecommendBookIdsKey(1,true);
  1520. $redis->del($key);
  1521. $key = $this->_shelfRecommendBookIdsKey(1,true);
  1522. $redis->del($key);
  1523. return $this->getReturn();
  1524. } catch (\Exception $e) {
  1525. return $this->setCode(ErrorCodeConstants::EXCEPTION)->setData($e->getMessage())->getReturn();
  1526. }
  1527. }
  1528. /**
  1529. * 清除文末书籍推荐缓存
  1530. */
  1531. public function clearNovelEndRecommendBookIds()
  1532. {
  1533. try {
  1534. $redis = Redis::instance();
  1535. $key = $this->_novelEndRecommendBookIdsKey(1);
  1536. $redis->del($key);
  1537. $key = $this->_novelEndRecommendBookIdsKey(2);
  1538. $redis->del($key);
  1539. $redis->del('NERC:W:' . 1);
  1540. $redis->del('NERC:W:' . 2);
  1541. return $this->getReturn();
  1542. } catch (\Exception $e) {
  1543. return $this->setCode(ErrorCodeConstants::EXCEPTION)->setData($e->getMessage())->getReturn();
  1544. }
  1545. }
  1546. /**
  1547. * 构造用户加入书架书籍id缓存的key
  1548. * @param $userId
  1549. * @return string
  1550. */
  1551. private function _shelfBooksKey($userId)
  1552. {
  1553. return 'SBIK:' . $userId;
  1554. }
  1555. /**
  1556. * 构造书架推荐书籍id缓存的key
  1557. * @param $sex
  1558. * @return string
  1559. */
  1560. private function _shelfRecommendBookIdsKey($sex,$isWater=false)
  1561. {
  1562. return $isWater ? 'BEHD:W:' . $sex : 'BEHD:' . $sex;
  1563. }
  1564. /**
  1565. * 构造文末书架推荐书籍id缓存的key
  1566. * @param $sex
  1567. * @return string
  1568. */
  1569. private function _novelEndRecommendBookIdsKey($sex)
  1570. {
  1571. return 'NERC:' . $sex;
  1572. }
  1573. /**
  1574. * 加入书架
  1575. * @param $bookId string 书籍id
  1576. * @param $userId
  1577. * @return ReturnObject
  1578. */
  1579. public function setBookShelf($bookId, $userId = null)
  1580. {
  1581. try {
  1582. if (empty($userId)) {
  1583. $userId = UserService::instance()->getUserInfo()->id;
  1584. }
  1585. $recentlyReadModel = $this->getUserRecentlyRead();
  1586. $usKey = $recentlyReadModel->getUSKey($userId);
  1587. $ubKey = $recentlyReadModel->getUBKey($userId, $bookId);
  1588. $redis = Redis::instance();
  1589. $RecentlyReadDB = $this->getUserRecentlyRead()->setConnect($userId);
  1590. if (!($userRead = $redis->hGetAll($ubKey))) {
  1591. $userRead = $RecentlyReadDB->getone($userId, $bookId);
  1592. $userRead = $userRead ? $userRead->toArray() : null;
  1593. }
  1594. $userRecentInfo = [
  1595. 'updatetime' => time(),
  1596. 'book_shelf_add' => 1,
  1597. 'book_shelf_flag' => 1,
  1598. 'flag' => 0,//用户主动加入书架,阅读记录不生效
  1599. ];
  1600. if (!empty($userRead)) { //更新
  1601. $getId = $userRead['id'];
  1602. $RecentlyReadDB->where(['id' => $getId])->update($userRecentInfo);
  1603. $userRecentInfo = array_merge($userRead, $userRecentInfo);//构造redis用的数据
  1604. } else {//创建阅读记录
  1605. $userRecentInfo['user_id'] = $userId;
  1606. $userRecentInfo['book_id'] = $bookId;
  1607. $userRecentInfo['createtime'] = time();
  1608. $userRecentInfo['chapter_id'] = 0;
  1609. $userRecentInfo['chapter_name'] = "";
  1610. $RecentlyReadDB->insert($userRecentInfo);
  1611. $id = $RecentlyReadDB->getLastInsID();
  1612. $userRecentInfo['id'] = $id;
  1613. if (!$redis->exists($usKey)) { //(如果redis里最近阅读记录为空拉取一下最近阅读记录防灾)
  1614. $RecentlyReadDB->getRecentlyRead(time() + 1, 10, $userId);
  1615. }
  1616. }
  1617. $redis->hMSet($ubKey, $userRecentInfo);
  1618. $redis->expire($ubKey, 43200);
  1619. $redis->zAdd($usKey, $userRecentInfo['updatetime'], $ubKey);
  1620. $redis->expire($usKey, 43200);
  1621. $this->delRecommendShelfBookByBookId($bookId, $userId);
  1622. return $this->getReturn();
  1623. } catch (\Exception $e) {
  1624. return $this->getExceptionReturn($e);
  1625. }
  1626. }
  1627. /**
  1628. * 用户删除书架上的书籍
  1629. * @param array $bookIds 书籍id列表
  1630. * @return ReturnObject
  1631. */
  1632. public function delBookShelf($bookIds)
  1633. {
  1634. if (empty($bookIds)) {
  1635. return $this->setCode(ErrorCodeConstants::API_ERROR)->setMsg('删除书籍id为空')->getReturn();
  1636. }
  1637. $userInfo = UserService::instance()->getUserInfo();
  1638. $userId = $userInfo->id;
  1639. $userRecentModel = $this->getUserRecentlyRead();
  1640. try {
  1641. #region 书架分库里删除数据
  1642. $this->getBookShelfModel()->setConnect($userId)->where('user_id', $userId)
  1643. ->whereIn('book_id', $bookIds)
  1644. ->where('flag', Common::YES)
  1645. ->update(['flag' => Common::NO, 'updatetime' => time()]);
  1646. //删除书架书籍id列表缓存 & 删除书架排除书籍id列表缓存
  1647. $redis = Redis::instance();
  1648. $shelfBookIdsKey = $this->_shelfBooksKey($userId);
  1649. $redis->del($shelfBookIdsKey);
  1650. #endregion
  1651. #region 阅读记录表里删除数据
  1652. $recentlyRead = $userRecentModel->setConnect($userId);
  1653. $recentlyRead->where('user_id', $userId)->whereIn('book_id', $bookIds)->update([
  1654. 'book_shelf_add' => 1,
  1655. 'book_shelf_flag' => 0
  1656. ]);
  1657. #endregion
  1658. #region 删除U-B的数据
  1659. $ubKeys = [];
  1660. foreach ($bookIds as $bookId) {
  1661. $ubKeys[] = $userRecentModel->getUBKey($userId, $bookId);
  1662. }
  1663. $getUBRedisIndex = Redis::splitKeysByRule($ubKeys);
  1664. array_walk($getUBRedisIndex, function ($v, $k) {
  1665. $redis = Redis::getRedisConnect($k);
  1666. $redis->del(...$v);
  1667. });
  1668. #endregion
  1669. #region 在U-S中删除U-B的元素
  1670. $usKey = $userRecentModel->getUSKey($userId);
  1671. $redis->zRem($usKey, ...$ubKeys);
  1672. #endregion
  1673. return $this->getReturn();
  1674. } catch (\Exception $e) {
  1675. return $this->setCode(ErrorCodeConstants::EXCEPTION)->getReturn();
  1676. }
  1677. }
  1678. /**
  1679. * 检查用户是否将书籍加入书架
  1680. * @param $bookId
  1681. * @return ReturnObject
  1682. */
  1683. public function checkBookInShelf($bookId)
  1684. {
  1685. try {
  1686. $shelfBooks = $this->getRecommendShelfBooks();
  1687. $result = ArrayHelper::array_column_search($shelfBooks, 'book_id', $bookId);
  1688. if (!empty($result)) {
  1689. return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(true)->getReturn();
  1690. }
  1691. $userInfo = UserService::instance()->getUserInfo();
  1692. $userId = $userInfo->id;
  1693. $usKey = $this->getUserRecentlyRead()->getUSKey($userId);
  1694. $ubKey = $this->getUserRecentlyRead()->getUBKey($userId, $bookId);
  1695. $redis = Redis::instance();
  1696. $score = $redis->zScore($usKey, $ubKey);
  1697. if (empty($score)) {
  1698. $bookShelfList = $this->_getBookShelf(0, 100);
  1699. $match = ArrayHelper::array_column_search($bookShelfList, 'book_id', $bookId);
  1700. if (empty($match)) {
  1701. return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(false)->getReturn();
  1702. } else {
  1703. return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(true)->getReturn();
  1704. }
  1705. } else {
  1706. return $this->setCode(ErrorCodeConstants::SUCCESS)->setData(true)->getReturn();
  1707. }
  1708. } catch (\Exception $e) {
  1709. return $this->setCode(ErrorCodeConstants::EXCEPTION)->setMsg($e->getMessage())->getReturn();
  1710. }
  1711. }
  1712. /**
  1713. * 获取当前渠道商是否展示书架功能
  1714. * @return bool
  1715. */
  1716. public function showBookShelfFun()
  1717. {
  1718. /*$showBookShelf = false;
  1719. $bookShelfChannelWhitelist = config('site.book_shelf_channel_whitelist');
  1720. if ($bookShelfChannelWhitelist == '-1') {
  1721. $showBookShelf = true;
  1722. } elseif ($bookShelfChannelWhitelist == '0') {
  1723. $showBookShelf = false;
  1724. } else {
  1725. $aBookShelfChannelWhite = explode(',', $bookShelfChannelWhitelist);
  1726. $channel_id = UserService::instance()->getUserInfo()->channel_id;
  1727. if (in_array($channel_id, $aBookShelfChannelWhite)) {
  1728. $showBookShelf = true;
  1729. }
  1730. }
  1731. return $showBookShelf;*/
  1732. $channel_id = UserService::instance()->getUserInfo()->channel_id;
  1733. return model("ChannelSpecialManage")->isWhite("show_shelf", $channel_id);
  1734. }
  1735. #endregion
  1736. /**
  1737. * 获取章末推荐书籍
  1738. * @param $book_id
  1739. * @param $chapter_id
  1740. * @return ReturnObject
  1741. */
  1742. public function getChapterEndRecommendBook($book_id, $chapter_id)
  1743. {
  1744. $return = false;
  1745. $cacheKey = CacheConstants::getChapterEndRecommendIds($book_id);
  1746. $book_info = [];
  1747. if ($list = Redis::instance()->zRangeByScore($cacheKey, 0, $chapter_id)) {
  1748. foreach ($list as $index=>$item) {
  1749. $book_info[$index] = json_decode($item, true);
  1750. }
  1751. } else {
  1752. $list = $this->getChapterEndRecommendModel()
  1753. ->alias('ce')
  1754. ->join('book', 'book.id=ce.relation_book_id')
  1755. ->where('book.state', BookConstants::BOOK_STATE_ON_SALE)
  1756. ->where('ce.book_id', $book_id)
  1757. ->where('ce.status', 'normal')
  1758. ->field('ce.relation_book_id, ce.chapter_show, ce.recommend, ce.tip')
  1759. ->select();
  1760. if ($list) {
  1761. foreach ($list as $item) {
  1762. Redis::instance()->zAdd($cacheKey, $item['chapter_show'], json_encode($item->getData(), JSON_UNESCAPED_UNICODE));
  1763. }
  1764. foreach ($list as $index=>$item) {
  1765. $book_info[$index] = $item->getData();
  1766. }
  1767. } else {
  1768. Redis::instance()->zAdd($cacheKey, 0, '{"chapter_show":0}');
  1769. }
  1770. Redis::instance()->expire($cacheKey, 600);
  1771. }
  1772. $end_close = Cookie::get('chapter_end-'.$book_id);
  1773. if ($end_close) {
  1774. $close_ids = explode('.', $end_close);
  1775. } else {
  1776. $close_ids = [];
  1777. }
  1778. $book_info = array_filter($book_info, function ($v) use ($chapter_id, $close_ids) {
  1779. return $v['chapter_show'] && $v['chapter_show'] <= $chapter_id && !in_array($v['relation_book_id'], $close_ids);
  1780. });
  1781. if ($book_info) {
  1782. $random = ArrayHelper::array_random($book_info);
  1783. $mBook = \app\common\service\BookService::instance()->getBookModel();
  1784. $book = $mBook->BookInfo($random['relation_book_id']);
  1785. if ($book) {
  1786. $return = array_merge($book, $random);
  1787. if (!$return['recommend']) {
  1788. $return['recommend'] = $book['description'];
  1789. }
  1790. if (mb_strlen($return['recommend']) > 30) {
  1791. $return['recommend'] = mb_substr($return['recommend'], 0, 30) . '...';
  1792. }
  1793. }
  1794. }
  1795. return $this->setData($return)->getReturn();
  1796. }
  1797. /*
  1798. * 榜单列表 rank1男频 rank2女频 rank0不区分男女 按照idx排序
  1799. */
  1800. public function ranklist($type, $is_water)
  1801. {
  1802. if($is_water){
  1803. $join = ' and book.classify_white_list=1';
  1804. $key0 = 'RANK:0:W';
  1805. $key1 = 'RANK:1:W';
  1806. $key2 = 'RANK:2:W';
  1807. }else{
  1808. $join = '';
  1809. $key0 = 'RANK:0';
  1810. $key1 = 'RANK:1';
  1811. $key2 = 'RANK:2';
  1812. }
  1813. $boydata = [];
  1814. $girldata = [];
  1815. if (Redis::instance()->exists($key1) && Redis::instance()->exists($key2) && Redis::instance()->exists($key0)) {
  1816. $boydata = json_decode(Redis::instance()->get($key1), true);
  1817. $girldata = json_decode(Redis::instance()->get($key2), true);
  1818. $idxdata = json_decode(Redis::instance()->get($key0), true);
  1819. } else {
  1820. $boydata['click'] = collection(
  1821. model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT')
  1822. ->where(['book.state' => 1, 'book.sex' => 1])
  1823. ->field('book.*,bc.name as bc_name')
  1824. ->order('book.read_num desc')
  1825. ->limit(10)
  1826. ->select()
  1827. )->toArray();
  1828. foreach ($boydata['click'] as &$value) {
  1829. $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']);
  1830. $value['read_nums'] = friend_date($value['read_num']);
  1831. $value['author'] = $value['bc_name'];
  1832. }
  1833. $boydata['idx'] = collection(
  1834. model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT')
  1835. ->where(['book.state' => 1, 'book.sex' => 1])
  1836. ->field('book.*,bc.name as bc_name')
  1837. ->order('book.idx desc')
  1838. ->limit(10)
  1839. ->select()
  1840. )->toArray();
  1841. foreach ($boydata['idx'] as &$value) {
  1842. $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']);
  1843. $value['read_nums'] = friend_date($value['read_num']);
  1844. $value['author'] = $value['bc_name'];
  1845. }
  1846. $girldata['click'] = collection(
  1847. model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT')
  1848. ->where(['book.state' => 1, 'book.sex' => 2])
  1849. ->field('book.*,bc.name as bc_name')
  1850. ->order('book.read_num desc')
  1851. ->limit(10)
  1852. ->select()
  1853. )->toArray();
  1854. foreach ($girldata['click'] as &$value) {
  1855. $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']);
  1856. $value['read_nums'] = friend_date($value['read_num']);
  1857. $value['author'] = $value['bc_name'];
  1858. }
  1859. $girldata['idx'] = collection(
  1860. model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT')
  1861. ->where(['book.state' => 1, 'book.sex' => 2])
  1862. ->field('book.*,bc.name as bc_name')
  1863. ->order('book.idx desc')
  1864. ->limit(10)
  1865. ->select()
  1866. )->toArray();
  1867. foreach ($girldata['idx'] as &$value) {
  1868. $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']);
  1869. $value['read_nums'] = friend_date($value['read_num']);
  1870. $value['author'] = $value['bc_name'];
  1871. }
  1872. $idxdata['idx'] = collection(
  1873. model('Book')->join('book_category bc','bc.id = book.book_category_id'.$join,'LEFT')
  1874. ->where(['book.state' => 1])
  1875. ->field('book.*,bc.name as bc_name')
  1876. ->order('book.idx desc')
  1877. ->limit(10)
  1878. ->select()
  1879. )->toArray();
  1880. foreach ($idxdata['idx'] as &$value) {
  1881. $value['vip_pay'] = BookService::instance()->showCpVipSubScript($value['id']);
  1882. $value['read_nums'] = friend_date($value['read_num']);
  1883. $value['author'] = $value['bc_name'];
  1884. }
  1885. if (!empty($boydata)) {
  1886. Redis::instance()->setex($key1, 600, json_encode($boydata, JSON_UNESCAPED_UNICODE));
  1887. }
  1888. if (!empty($girldata)) {
  1889. Redis::instance()->setex($key2, 600, json_encode($girldata, JSON_UNESCAPED_UNICODE));
  1890. }
  1891. if (!empty($idxdata)) {
  1892. Redis::instance()->setex($key0, 600, json_encode($idxdata, JSON_UNESCAPED_UNICODE));
  1893. }
  1894. }
  1895. if ($type == 1) {
  1896. return $boydata;
  1897. } elseif ($type == 2) {
  1898. return $girldata;
  1899. } elseif ($type == 'boy') {
  1900. return $boydata;
  1901. } elseif ($type == 'girl') {
  1902. return $girldata;
  1903. } else {
  1904. return $idxdata;
  1905. }
  1906. }
  1907. /*
  1908. * 主编推荐列表
  1909. */
  1910. public function recommendList($book_id)
  1911. {
  1912. if($book_id){
  1913. $bookInfo = model("book")->BookInfo($book_id);
  1914. }else{
  1915. return false;
  1916. }
  1917. $where = [];
  1918. $where['state'] = '1';
  1919. $where['book_category_id'] = $bookInfo['book_category_id'];
  1920. //最近阅读
  1921. $recentList = model('UserRecentlyRead')->getRecentlyRead(0);
  1922. $recentIds[] = $bookInfo['id'];
  1923. if ($recentList['totalNum'] > 0) {
  1924. foreach ($recentList['data'] as $v) {
  1925. $recentIds[] = $v['book_id'];
  1926. }
  1927. }
  1928. if ($recentIds) {
  1929. $where['id'] = ['not in', $recentIds];
  1930. }
  1931. $pagedata[0]['name'] = "主编推荐";
  1932. $pagedata[0]['page_id'] = 1;
  1933. $pagedata[0]['second_name'] = "主编推荐";
  1934. $pagedata[0]['block_resource'] = collection(model("Book")
  1935. ->where($where)
  1936. ->order('rand()')
  1937. ->limit(4)
  1938. ->select())->toArray();
  1939. if($pagedata[0]['block_resource']){
  1940. foreach ($pagedata[0]['block_resource'] as &$v){
  1941. $v['book_id'] = $v['id'];
  1942. }
  1943. }
  1944. return $pagedata;
  1945. }
  1946. }