Book.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. <?php
  2. namespace app\common\model;
  3. use app\common\library\Redis;
  4. use app\common\service\GussNovelService;
  5. use app\main\service\BookService;
  6. use app\common\service\BookRelationService;
  7. use BookCategory;
  8. use think\Config as ConfigAs;
  9. use think\Db;
  10. use think\Log;
  11. use think\Model;
  12. class Book extends BaseRwModel
  13. {
  14. // 表名
  15. protected $table = 'book';
  16. // 自动写入时间戳字段
  17. protected $autoWriteTimestamp = 'int';
  18. // 定义时间戳字段名
  19. protected $createTime = 'createtime';
  20. protected $updateTime = 'updatetime';
  21. // 追加属性
  22. protected $append = [
  23. 'state_text',
  24. 'free_stime_text',
  25. 'free_etime_text',
  26. 'last_chapter_utime_text',
  27. 'sex_text',
  28. 'category_text',
  29. 'billing_type_text',
  30. 'is_finish_text',
  31. 'corner_mark_text',
  32. 'cansee_text',
  33. 'rank_text',
  34. 'check_rank_text',
  35. 'is_expire',
  36. 'expire_time_text',
  37. ];
  38. public function getStateList()
  39. {
  40. return ['1' => '上架', '0' => '下架', '-1' => '入库'];
  41. }
  42. public function getCanseeList()
  43. {
  44. return ['1' => '可见', '0' => '不可见'];
  45. }
  46. public function getSexList()
  47. {
  48. return ['1' => '男频', '2' => '女频'];
  49. }
  50. public function getBillingTypeList()
  51. {
  52. return ['1' => '章', '2' => '本'];
  53. }
  54. public function getIsFinishList()
  55. {
  56. return ['0' => '连载', '1' => '完本'];
  57. }
  58. public function getCornerMarkList()
  59. {
  60. return ['hot' => '火热在推', 'exclusive' => '独家', 'new' => '新书'];
  61. }
  62. public function getIsExpireAttr($value,$data)
  63. {
  64. if(empty($data['expire_time'])){
  65. return 0;
  66. }
  67. if ($data['expire_time'] - time() < 3600*24*30){
  68. return 1;
  69. }
  70. return 2;
  71. }
  72. public function getExpireTimeTextAttr($value,$data)
  73. {
  74. return date('Y-m-d H:i:s',$data['expire_time']);
  75. }
  76. public function getRankTextAttr($value, $data)
  77. {
  78. $status = ['1'=>'不通过','2'=>'一级敏感','3'=>'二级敏感','4'=>'通过','5'=>'不收录','6'=>'二级可上架','0'=>'没有评级'];
  79. return $status[$data['rank']];
  80. }
  81. public function getCheckRankTextAttr($value, $data)
  82. {
  83. $status = ['1'=>'不合格','2'=>'合格','0'=>'未质检'];
  84. return $status[$data['check_rank']];
  85. }
  86. public function getCategoryName($book_category_id)
  87. {
  88. $category = model('BookCategory')->info($book_category_id);
  89. return $category['name'];
  90. }
  91. public function getCategoryTextAttr($value, $data)
  92. {
  93. $value = $value ? $value : $data['book_category_id'];
  94. $category = model('BookCategory')->info($value);
  95. return $category['name'];
  96. }
  97. public function getStateTextAttr($value, $data)
  98. {
  99. $value = $value ? $value : $data['state'];
  100. $list = $this->getStateList();
  101. return isset($list[$value]) ? $list[$value] : '';
  102. }
  103. public function getCornerMarkTextAttr($value, $data)
  104. {
  105. $value = $value ? $value : $data['corner_mark'];
  106. $list = $this->getCornerMarkList();
  107. return isset($list[$value]) ? $list[$value] : '';
  108. }
  109. public function getFreeStimeTextAttr($value, $data)
  110. {
  111. $value = $value ? $value : $data['free_stime'];
  112. return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
  113. }
  114. public function getFreeEtimeTextAttr($value, $data)
  115. {
  116. $value = $value ? $value : $data['free_etime'];
  117. return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
  118. }
  119. public function getLastChapterUtimeTextAttr($value, $data)
  120. {
  121. $value = $value ? $value : $data['last_chapter_utime'];
  122. return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
  123. }
  124. public function getSexTextAttr($value, $data)
  125. {
  126. $value = $value ? $value : $data['sex'];
  127. $list = $this->getSexList();
  128. return isset($list[$value]) ? $list[$value] : '';
  129. }
  130. public function getCanSeeTextAttr($value, $data)
  131. {
  132. $value = $value ? $value : $data['cansee'];
  133. $list = $this->getCanseeList();
  134. return isset($list[$value]) ? $list[$value] : '';
  135. }
  136. public function getBillingTypeTextAttr($value, $data)
  137. {
  138. $value = $value ? $value : $data['billing_type'];
  139. $list = $this->getBillingTypeList();
  140. return isset($list[$value]) ? $list[$value] : '';
  141. }
  142. public function getIsFinishTextAttr($value, $data)
  143. {
  144. $value = $value ? $value : $data['is_finish'];
  145. $list = $this->getIsFinishList();
  146. return isset($list[$value]) ? $list[$value] : '';
  147. }
  148. protected function setFreeStimeAttr($value)
  149. {
  150. return $value && !is_numeric($value) ? strtotime($value) : $value;
  151. }
  152. protected function setFreeEtimeAttr($value)
  153. {
  154. return $value && !is_numeric($value) ? strtotime($value) : $value;
  155. }
  156. protected function setLastChapterUtimeAttr($value)
  157. {
  158. return $value && !is_numeric($value) ? strtotime($value) : $value;
  159. }
  160. public function BookCategory()
  161. {
  162. return $this->belongsTo('BookCategory', 'book_category_id', 'id')->setEagerlyType(0);
  163. }
  164. /*
  165. * 检查书籍信息
  166. */
  167. public static function checkBookInfo($book)
  168. {
  169. $msg = '';
  170. //如果是按本收费,必须有价格。
  171. if ($book['billing_type'] == '2') {
  172. if (empty($book['price']) || $book['price'] <= 0) {
  173. return $msg = '书籍' . $book['id'] . '按本收费书籍必须填写价格';
  174. }
  175. }
  176. //名称、作者、封面、字数、描述、完结状态、首末章节信息
  177. if (empty($book['name'])) {
  178. return $msg = '书籍' . $book['id'] . '接口获取不到书籍名称';
  179. }
  180. if (empty($book['author'])) {
  181. return $msg = '书籍' . $book['id'] . '接口获取不到作者信息';
  182. }
  183. if (empty($book['image'])) {
  184. return $msg = '书籍' . $book['id'] . '接口获取不到封面';
  185. }
  186. if (empty($book['description'])) {
  187. return $msg = '书籍' . $book['id'] . '接口获取不到书籍描述';
  188. }
  189. if (empty($book['chapter_num'])) {
  190. return $msg = '书籍' . $book['id'] . '接口获取不到章节数';
  191. }
  192. if (empty($book['first_chapter_id']) || $book['first_chapter_id'] <= 0) {
  193. return $msg = '书籍' . $book['id'] . '接口获取不到章节数';
  194. }
  195. return $msg;
  196. }
  197. /**
  198. * 书籍信息 Redis缓存永久
  199. */
  200. public function BookInfo($id)
  201. {
  202. $redis = Redis::instance();
  203. $key = 'B:' . $id;
  204. if ($redis->exists($key)) {
  205. $info = $redis->hgetall($key);
  206. if (!is_array($info) || count($info) < 5 ) {
  207. $redis->del($key);
  208. $info = $this->get($id);
  209. if ($info) {
  210. $info = $info->toArray();
  211. //维护书籍互斥关系 wudd 《前台不做维护-20200115》 || !isset($info['relation_id'])
  212. //$info['relation_id']= BookRelationService::instance()->getBookRelationById( $id );
  213. $redis->hmset($key, $info);
  214. } else {
  215. return '';
  216. }
  217. }
  218. } else {
  219. $info = $this->get($id);
  220. if ($info) {
  221. $info = $info->toArray();
  222. //维护书籍互斥关系 wudd 《前台不做维护-20200115》
  223. //$info['relation_id']= BookRelationService::instance()->getBookRelationById( $id );
  224. $redis->hmset($key, $info);
  225. } else {
  226. return '';
  227. }
  228. }
  229. return $info;
  230. }
  231. /**************************通过API获取书籍信息***********************/
  232. /**
  233. * 根据书籍id获取存储路径
  234. *
  235. * @param $book_id
  236. * return string
  237. */
  238. protected static function getPath($book_id)
  239. {
  240. $path = '/' . substr($book_id, 0, 1) . 'x' . substr($book_id, 1, 1)
  241. . '/' . substr($book_id, 0, 2) . 'x' . substr($book_id, 2, 1)
  242. . '/' . substr($book_id, 0, 3) . 'x' . substr($book_id, 3, 1)
  243. . '/' . $book_id . '/';
  244. return $path;
  245. }
  246. /**
  247. * 获取单本书籍信息
  248. *
  249. * @param int $book_id 书籍ID
  250. * @return array
  251. */
  252. public static function getBookInfo($book_id)
  253. {
  254. $lastChapter = self::getChapterLimit($book_id, -1, -1);
  255. if (empty($lastChapter)) {
  256. Log::notice('获取末章信息失败!书籍ID:' . $book_id);
  257. return ['code' => 100];
  258. }
  259. $lastChapter = current($lastChapter);
  260. $firstChapter = self::getChapterLimit($book_id, 0, 0);
  261. if (empty($firstChapter)) {
  262. Log::notice('获取首章信息失败!书籍ID:' . $book_id);
  263. return ['code' => 100];
  264. }
  265. $firstChapter = current($firstChapter);
  266. $chapterCount = self::getChapterCount($book_id);
  267. if(ConfigAs::get('redis.change') == 1){
  268. $code = self::getCode('A03'.$book_id);
  269. $redis = Redis::instanceBookChange($code);
  270. $book = $redis->get("A03{$book_id}");
  271. $book = json_decode($book, true);
  272. }else {
  273. $redis = Redis::instanceBook();
  274. $book = $redis->get("basedata_book_{$book_id}");
  275. $book = json_decode(gzdecode($book), true);
  276. }
  277. if (strpos($book['coverWap'], 'cppartner') === false) {
  278. $cover = ConfigAs::get('api.cdn') . "/cppartner" . self::getPath($book['bookId']) . $book['coverWap'];
  279. } else {
  280. $cover = ConfigAs::get('api.cdn') . $book['coverWap'];
  281. }
  282. $extend = [];
  283. if (isset($book['extend'])){
  284. $extend = !is_array($book['extend'])?json_decode($book['extend'],true):$book['extend'];
  285. }
  286. $data = [
  287. "author" => $book['author'],
  288. "word_num" => $book['totalWordSize'] ?: 0,
  289. "first_chapter_id" => $firstChapter['id'],
  290. "first_chapter_name" => $firstChapter['name'],
  291. "last_chapter_id" => $book['lastChapterId'] ?? $lastChapter['id'],
  292. "last_chapter_name" => $book['lastChapterName'] ?? $lastChapter['name'],
  293. "last_chapter_utime" => empty($book['lastChapterUtime']) ? time() : strtotime($book['lastChapterUtime']),
  294. "description" => $book['introduction'],
  295. "chapter_num" => $chapterCount,
  296. "cover"=>$cover,
  297. "read_num" => $book['clickNum'],
  298. "name" => $book['bookName'],
  299. "is_finish" => strstr($book['status'], '连') === false ? 1 : 0,
  300. "id" => $book['bookId'],
  301. //wud 书籍互斥关系
  302. "childBookId"=>$book['childBookId'] ?? [],
  303. "parentBookId"=>$book['parentBookId'] ?? '',
  304. //同步书籍评级和质检状态 及质检备注
  305. "rank"=> $extend['grade'] ?? 0,
  306. "check_rank"=> !isset($extend['qaStatus'])? 0 : ($extend['qaStatus'] == 1 ? 2 : 1),
  307. 'check_remark'=>isset($extend['remark'])?substr($extend['remark'],0,140):'',
  308. 'cp_name'=>$book['cpPartnerName']??'',
  309. 'cp_id'=>$book['cpPartnerId']??0,
  310. 'tags' => $book['tag'] ?? '',
  311. ];
  312. return [
  313. 'data' => $data,
  314. 'code' => 0,
  315. 'msg' => 'success'
  316. ];
  317. // return self::getApi('bookinfo.do', ['book_id' => $book_id]);
  318. }
  319. /**
  320. * 获取多本书籍信息
  321. *
  322. * @param string $book_ids 书籍ID,书籍ID,书籍ID
  323. * @return array
  324. */
  325. public static function getBookInfos($book_ids)
  326. {
  327. $data = [];
  328. $ids = explode(',', $book_ids);
  329. foreach ($ids as $id) {
  330. if ($id) {
  331. $book = self::getBookInfo($id);
  332. if ($book['code'] != 0) {
  333. Log::notice('获取书籍信息失败!书籍ID:' . $id);
  334. return ['code' => 100];
  335. }
  336. $data[] = $book['data'];
  337. }
  338. }
  339. return [
  340. 'data' => $data,
  341. 'code' => 0,
  342. 'msg' => 'success'
  343. ];
  344. // return self::getApi('bookinfos.do', ['book_ids' => $book_ids]);
  345. }
  346. public static function getCode($val){
  347. $bKey = md5($val,true);
  348. $rv = (ord($bKey[3]) & 0xFF) << 24
  349. | (ord($bKey[2]) & 0xFF) << 16
  350. | (ord($bKey[1]) & 0xFF) << 8
  351. | ord($bKey[0]) & 0xFF;
  352. return $rv & 0xffffffff;
  353. }
  354. /**
  355. * 获取书籍章节信息
  356. * @deprecated use searchChapterByName getChapterList getChapterLimit
  357. * @param int $book_id 书籍ID
  358. * @param int $page_no 第几页
  359. * @param int $limit 单页条数
  360. * @return array
  361. */
  362. public static function getChapterListOld($book_id, $page_no = 1, $limit = 20, $search = '')
  363. {
  364. if(ConfigAs::get('redis.change') == 1){
  365. //Log::write('redis.change = 1','cctest');
  366. $code = self::getCode('A04'.$book_id);
  367. //Log::write('测试code = '.$code,'cctest');
  368. $redis = Redis::instanceBookChange($code);
  369. $list = $redis->zRange('A04' . $book_id, 0, -1);
  370. //dump($list);die;
  371. }else{
  372. $redis = Redis::instanceBook();
  373. $list = $redis->lRange("basedata_chapter_owchcp_{$book_id}", 0, -1);
  374. }
  375. $all = []; //全部章节
  376. $data = [
  377. 'data' => [],
  378. 'pageNo' => $page_no,
  379. 'limit' => $limit,
  380. 'totalNum' => 0,
  381. 'totalPage' => 0
  382. ];
  383. foreach ($list as $key => $value) {
  384. if(ConfigAs::get('redis.change') == 1){
  385. $json = json_decode($value, true);
  386. }else{
  387. $json = json_decode(gzdecode($value), true);
  388. }
  389. $chapter = [
  390. 'id' => $json[4] ?? $json['chapterId'],
  391. 'idx' => count($all) + 1,
  392. 'name' => $json[5] ?? $json['chapterName']
  393. ];
  394. if ($search) {
  395. if (!strstr($chapter['name'], $search)) {
  396. continue;
  397. }
  398. }
  399. $all[] = $chapter;
  400. }
  401. if ($limit > 0) {
  402. $data['data'] = array_slice($all, ($page_no - 1) * $limit, $limit);
  403. } else { //获取全部
  404. $data['data'] = $all;
  405. $data['limit'] = count($all);
  406. }
  407. if (!count($data['data'])) {
  408. Log::notice('获取章节列表失败!书籍ID:' . $book_id . ' 分页:' . $page_no . ' 条数:' . $limit);
  409. return $data;
  410. }
  411. $data['totalNum'] = count($all);
  412. $data['totalPage'] = ceil(count($all) / $limit);
  413. return [
  414. 'data' => $data,
  415. 'code' => 0,
  416. 'msg' => 'success'
  417. ];
  418. // return self::getApi('chapterlist.do', ['book_id' => $book_id, 'page_no' => $page_no, 'limit' => $limit]);
  419. }
  420. /**
  421. * 书籍章节列表redis的key
  422. * @param string $bookId 书籍id
  423. * @return string
  424. */
  425. private static function getChapterKey($bookId)
  426. {
  427. return 'A04' . $bookId;
  428. }
  429. /**
  430. * 通过章节名称搜索章节,此方法需要获取书籍所有章节列表,请不要在前台使用
  431. * @param $bookId
  432. * @param int $pageNo
  433. * @param int $limit
  434. * @param $name
  435. * @return array
  436. */
  437. public static function searchChapterByName($bookId, $pageNo = 1, $limit = 20, $name)
  438. {
  439. if (empty($name)) {
  440. return self::getChapterList($bookId, $pageNo, $limit);
  441. }
  442. $chapterKey = self::getChapterKey($bookId);
  443. $code = self::getCode($chapterKey);
  444. $redis = Redis::instanceBookChange($code);
  445. $list = $redis->zRange($chapterKey, 0, -1);
  446. $all = [];
  447. $data = [
  448. 'data' => [],
  449. 'pageNo' => $pageNo,
  450. 'limit' => $limit,
  451. 'totalNum' => 0,
  452. 'totalPage' => 0
  453. ];
  454. foreach ($list as $key => $value) {
  455. $json = json_decode($value, true);
  456. $chapter = [
  457. 'id' => $json[4] ?? $json['chapterId'],
  458. 'idx' => $key + 1,
  459. 'name' => $json[5] ?? $json['chapterName']
  460. ];
  461. if (!strstr($chapter['name'], $name)) {
  462. continue;
  463. }
  464. $all[] = $chapter;
  465. }
  466. if ($limit > 0) {
  467. $data['data'] = array_slice($all, ($pageNo - 1) * $limit, $limit);
  468. } else { //获取全部
  469. $data['data'] = $all;
  470. $data['limit'] = count($all);
  471. }
  472. if (!count($data['data'])) {
  473. Log::notice('获取章节列表失败!书籍ID:' . $bookId . ' 分页:' . $pageNo . ' 条数:' . $limit);
  474. return ['code' => 100];
  475. }
  476. $data['totalNum'] = count($all);
  477. $data['totalPage'] = ceil(count($all) / $limit);
  478. return [
  479. 'data' => $data,
  480. 'code' => 0,
  481. 'msg' => 'success'
  482. ];
  483. }
  484. /**
  485. * 获取书籍章节列表
  486. * @param $bookId
  487. * @param int $pageNo 当前页码
  488. * @param int $limit 每页显示条数
  489. * @return array
  490. */
  491. public static function getChapterList($bookId, $pageNo = 1, $limit = 20)
  492. {
  493. $chapterKey = self::getChapterKey($bookId);
  494. $code = self::getCode($chapterKey);
  495. $redis = Redis::instanceBookChange($code);
  496. $start = ($pageNo - 1) * $limit;
  497. $stop = $start + $limit - 1;
  498. $list = $redis->zRange($chapterKey, $start, $stop);
  499. $all = [];
  500. $data = [
  501. 'data' => [],
  502. 'pageNo' => $pageNo,
  503. 'limit' => $limit,
  504. 'totalNum' => 0,
  505. 'totalPage' => 0
  506. ];
  507. foreach ($list as $key => $value) {
  508. $json = json_decode($value, true);
  509. $chapter = [
  510. 'id' => $json[4] ?? $json['chapterId'],
  511. 'idx' => $start + $key + 1,
  512. 'name' => $json[5] ?? $json['chapterName']
  513. ];
  514. $all[] = $chapter;
  515. }
  516. $data['data'] = $all;
  517. $data['limit'] = count($all);
  518. if (!count($data['data'])) {
  519. Log::notice("获取章节列表失败!书籍ID:$bookId 分页:$pageNo 条数:$limit");
  520. return ['code' => 100];
  521. }
  522. $totalNum = self::getChapterCount($bookId);
  523. $data['totalNum'] = $totalNum;
  524. $data['totalPage'] = ceil($totalNum / $limit);
  525. return [
  526. 'data' => $data,
  527. 'code' => 0,
  528. 'msg' => 'success'
  529. ];
  530. }
  531. /**
  532. * 获取书籍章节数量
  533. * @param $bookId
  534. * @return int
  535. */
  536. public static function getChapterCount($bookId)
  537. {
  538. $chapterKey = self::getChapterKey($bookId);
  539. $code = self::getCode($chapterKey);
  540. $redis = Redis::instanceBookChange($code);
  541. $count = $redis->zCard($chapterKey);
  542. return $count;
  543. }
  544. /**
  545. * 按章节索引获取书籍章节列表
  546. * @param $bookId
  547. * @param $startIdx 起始索引,第一章的索引为0
  548. * @param $stopIdx 结束索引,最后一章的索引为-1,倒数第二章为-2
  549. * @return array
  550. */
  551. public static function getChapterLimit($bookId, $startIdx, $stopIdx)
  552. {
  553. $chapterKey = self::getChapterKey($bookId);
  554. $code = self::getCode($chapterKey);
  555. $redis = Redis::instanceBookChange($code);
  556. $list = $redis->zRange($chapterKey, $startIdx, $stopIdx);
  557. if (empty($list)) {
  558. Log::notice("获取章节列表失败!书籍ID:$bookId startIdx:$startIdx stopIdx:$stopIdx");
  559. return [];
  560. }
  561. foreach ($list as $key => $value) {
  562. $json = json_decode($value, true);
  563. $chapter = [
  564. 'id' => $json[4] ?? $json['chapterId'],
  565. 'idx' => $startIdx + $key + 1,
  566. 'name' => $json[5] ?? $json['chapterName']
  567. ];
  568. $all[] = $chapter;
  569. }
  570. return $all;
  571. }
  572. /**
  573. * 获取章节详情
  574. *
  575. * @param int $book_id 书籍ID
  576. * @param int $chapter_id 章节ID
  577. * @param int $type 分割格式 默认1<p> 2数组
  578. * @return array
  579. */
  580. public static function getChapterInfo($book_id, $chapter_id, $type = 1)
  581. {
  582. $chapterKey = self::getChapterKey($book_id);
  583. $code = self::getCode($chapterKey);
  584. $redis = Redis::instanceBookChange($code);
  585. $chapterList = $redis->zRangeByScore($chapterKey, $chapter_id, $chapter_id, array('limit' => array(0, 1)));
  586. if (empty($chapterList)) {
  587. Log::notice('获取章节列表失败!书籍ID:' . $book_id);
  588. return ['code' => 100];
  589. }
  590. $chapterInfoStr = current($chapterList);
  591. $chapterInfo = json_decode($chapterInfoStr, true);
  592. $data = [
  593. 'name' => $chapterInfo[5],
  594. 'idx' => $chapterInfo['k'] + 1,
  595. 'id' => $chapterInfo[4],
  596. ];
  597. $data['pre_id'] = $chapterInfo['l'] ?? '';
  598. $data['next_id'] = $chapterInfo['n'] ?? '';
  599. $redis = Redis::instance();
  600. $keyChapter = 'BCCT:' . $chapter_id;
  601. if ($type == 1 && $redis->exists($keyChapter)) {
  602. if (BookService::instance()->getChapterEditedModel()->getChapterFromDb($chapter_id)) {
  603. $data['from'] = 'db';
  604. } else {
  605. $data['from'] = 'cache';
  606. }
  607. $data['content'] = $redis->get($keyChapter);
  608. } else {
  609. $chapter_result = BookService::instance()->getChapter($book_id, $chapter_id)->data;
  610. $data['from'] = $chapter_result['from'];
  611. if (array_key_exists('name', $chapter_result)) {
  612. $data['name'] = $chapter_result['name'];
  613. }
  614. if ($content = $chapter_result['content']) {
  615. // if (false) {
  616. $content = str_replace("\r", '', $content);
  617. $arr = explode("\n", $content);
  618. $str = '';
  619. $newArr = [];
  620. foreach ($arr as &$value) {
  621. $value = ltrim($value);
  622. $value = mb_ereg_replace('^( | )+','', $value);
  623. $value = mb_convert_encoding($value, "UTF-8", "UTF-8"); //强制转换一次UTF-8编码
  624. if ($value) {
  625. if ($type == 1) {
  626. $str .= "<p>{$value}</p>";
  627. } else {
  628. $newArr[] = $value;
  629. }
  630. }
  631. }
  632. unset($value);
  633. if ($type == 1) {
  634. $data['content'] = $str;
  635. if (!empty($data['content'])) {
  636. $keyNum = 'BCN:' . $chapter_id . ':' . date('i');
  637. if ($redis->incr($keyNum) > 10) {
  638. $redis->setex($keyChapter, 86400, $data['content']);
  639. }
  640. $redis->expire($keyNum, 60);
  641. }
  642. } else {
  643. $data['content'] = $arr;
  644. }
  645. } else {
  646. Log::error('获取章节内容失败!书籍ID:' . $book_id . ' 章节ID:' . $chapter_id);
  647. if ($type == 1) {
  648. $data['content'] = '';
  649. } else {
  650. $data['content'] = [];
  651. }
  652. }
  653. }
  654. $json = json_decode($data['content'], true);
  655. if ($json) {
  656. $content = array_map(function ($item) {
  657. return '<p>' . $item . '</p>';
  658. }, $json);
  659. $data['content'] = implode('', $content);
  660. }
  661. return [
  662. 'data' => $data,
  663. 'code' => 0,
  664. 'msg' => 'success'
  665. ];
  666. // return self::getApi('chapterinfo.do', ['book_id' => $book_id, 'chapter_id' => $chapter_id]);
  667. }
  668. /**
  669. * 获取书籍头几章
  670. *
  671. * @param int $book_id 书籍ID
  672. * @param int $head 章节序号
  673. * @return array
  674. */
  675. public static function getHeadChapters($book_id, $head)
  676. {
  677. if ($head > 10) { //最多读取10章
  678. $head = 10;
  679. }
  680. $chapterList = self::getChapterLimit($book_id, 0, $head - 1);
  681. $data = [];
  682. foreach ($chapterList as $chapter) {
  683. $info = \app\main\service\BookService::instance()->getChapterInfo($book_id, $chapter['id']);
  684. if ($info->code != 0) {
  685. Log::error('获取章节内容失败!书籍ID:' . $book_id . ' 章节ID:' . $chapter['id']);
  686. return ['code' => 100];
  687. }
  688. if(is_string($info->data['content'])){
  689. preg_match_all('/<p>(.*?)<\/p>/', $info->data['content'], $match);
  690. if($match){
  691. $info->data['content'] = $match[1];
  692. $data[] = $info->data;
  693. }else{
  694. $data[] = $info->data;
  695. }
  696. }else{
  697. $data[] = $info->data;
  698. }
  699. }
  700. return [
  701. 'data' => $data,
  702. 'code' => 0,
  703. 'msg' => 'success'
  704. ];
  705. // return self::getApi('chapterinfos.do', ['book_id' => $book_id, 'head' => $head]);
  706. }
  707. /**
  708. * 批量获取书籍信息
  709. * @param array $ids 书籍id列表
  710. * @return array
  711. */
  712. public function getBooksInfo(array $ids)
  713. {
  714. if (empty($ids)) {
  715. return [];
  716. }
  717. $result = [];
  718. #region 从redis中获取书籍信息
  719. $keys = [];
  720. $dbIds = [];
  721. foreach ($ids as $id) {
  722. $keys[] = 'B:' . $id;
  723. }
  724. $getBooksInfoRedisIndex = Redis::splitKeysByRule($keys);
  725. $rdsBooksInfo = [];
  726. foreach ($getBooksInfoRedisIndex as $k => $v) {
  727. $redis = Redis::getRedisConnect($k);
  728. $pipe = $redis->multi(\Redis::PIPELINE);
  729. foreach ($v as $redisKey) {
  730. $pipe->hGetAll($redisKey);
  731. }
  732. $_rdsBooksInfo = $pipe->exec();
  733. $_rdsBooksInfo = array_filter($_rdsBooksInfo);
  734. $rdsBooksInfo = array_merge($rdsBooksInfo, $_rdsBooksInfo);
  735. }
  736. $rdsBookIds = [];
  737. foreach ($rdsBooksInfo as $item) {
  738. if (!is_array($item) || count($item) < 5) {
  739. continue;
  740. }
  741. $result[$item['id']] = $item;
  742. $rdsBookIds[] = $item['id'];
  743. }
  744. $dbIds = array_diff($ids, $rdsBookIds);
  745. #endregion
  746. #region 没有在redis中找到的书籍,从数据库中获取,并插入redis
  747. if (count($dbIds) > 0) {
  748. $dbBooksInfo = $this->whereIn('id', $dbIds)->select();
  749. if (count($dbBooksInfo) > 0) {
  750. $originBookInfo = [];
  751. $booksIdKey = [];
  752. foreach ($dbBooksInfo as $dbBookInfo) {
  753. $bookInfo = $dbBookInfo->toArray();
  754. //获取书籍的互斥关系 wdd 《前台不做维护-20200115》
  755. //$bookInfo['relation_id'] = BookRelationService::instance()->getBookRelationById( $bookInfo['id'] );
  756. $originBookInfo[$bookInfo['id']] = $bookInfo;
  757. $booksIdKey[] = 'B:' . $bookInfo['id'];
  758. }
  759. $writeBooksInfoRedisIndex = Redis::splitKeysByRule($booksIdKey);
  760. foreach ($writeBooksInfoRedisIndex as $k => $v) {
  761. $redis = Redis::getRedisConnect($k);
  762. $pipe = $redis->multi(\Redis::PIPELINE);
  763. foreach ($v as $redisKey) {
  764. $bookId = str_replace('B:', '', $redisKey);
  765. $result[$bookId] = $originBookInfo[$bookId];
  766. $pipe->hmset($redisKey, $originBookInfo[$bookId]);
  767. }
  768. $pipe->exec();
  769. }
  770. }
  771. }
  772. return $result;
  773. }
  774. /**
  775. * 获取作者其他书籍(随机选择一本书)
  776. * @param $expectBookid 书籍ID,排重
  777. * @param $author 作者名称
  778. * @param null $expectBookid
  779. */
  780. public function getBookIdByAuthor($author, $expectBookid = null, $sex = '1',$isWater=false)
  781. {
  782. $gussNovels = GussNovelService::instance()->getLikedNovels($sex,$isWater);
  783. // 跟猜你喜欢中的书籍进行滤重
  784. $gussBookIds = array_column($gussNovels, 'id');
  785. $waterWhere = $isWater ? ['classify_white_list'=>1] : [];
  786. $books = $this->where('author', 'eq', $author)
  787. ->where('state', 'eq', '1')
  788. ->where($waterWhere)
  789. ->select();
  790. if (!empty($expectBookid)) {
  791. foreach ($books as $k => $item) {
  792. if ($item['id'] == $expectBookid || in_array($item['id'],$gussBookIds)) {
  793. unset($books[$k]);
  794. }
  795. }
  796. }
  797. $books = array_values($books);
  798. if(empty($books)){
  799. return ;
  800. }else{
  801. $rand = rand(0, (count($books)-1));
  802. return $books[$rand]['id'];
  803. }
  804. }
  805. //获取当前渠道不能查看的书籍id
  806. public function getExclusiveBookIds($channel_id){
  807. $sql = 'select aeb.bid
  808. from exclusive_book aeb
  809. inner join exclusive ae on aeb.eid = ae.id and ae.endtime > '.time().' and ae.status = 1
  810. where aeb.bid not in(
  811. select distinct eb.bid
  812. from `exclusive_channel` as ec
  813. inner join `exclusive` as e on ec.eid = e.id and e.endtime > '.time().' AND e.status = 1
  814. inner join `exclusive_book` as eb on eb.eid = e.id
  815. where ec.cid = '.$channel_id.'
  816. ) and aeb.bid not in(
  817. select eb.bid from exclusive_book eb
  818. left join exclusive_channel ec on ec.eid=eb.eid
  819. where ec.id is null
  820. )';
  821. $rs = Db::query($sql);
  822. if(empty($rs)){
  823. return [];
  824. }
  825. return array_column($rs,'bid');
  826. }
  827. public function getVipExclusiveBookIds($channel_ids){
  828. $sql = 'select aeb.bid from exclusive_book aeb
  829. inner join exclusive ae on aeb.eid=ae.id and endtime>'.time().' and status=1 and
  830. aeb.bid not in(
  831. select eb.bid from exclusive_book eb
  832. inner join exclusive e on e.id=eb.eid and endtime>'.time().' and status=1
  833. inner join exclusive_channel ec on ec.eid=eb.eid
  834. where ec.cid in('.implode(',',$channel_ids).')
  835. )
  836. inner join exclusive_channel ec on ec.eid = ae.id';
  837. $rs = Db::query($sql);
  838. if(empty($rs)){
  839. return [];
  840. }
  841. return array_column($rs,'bid');
  842. }
  843. }