SyncChapters.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. <?php
  2. namespace App\Console\Sync;
  3. use App\Libs\BatchUpdateTrait;
  4. use App\Models\Book\Book;
  5. use App\Models\Book\Chapter;
  6. use App\Models\Book\ChapterContent;
  7. use Illuminate\Console\Command;
  8. use Illuminate\Support\Facades\DB;
  9. class SyncChapters extends Command
  10. {
  11. use BatchUpdateTrait;
  12. /**
  13. * The name and signature of the console command.
  14. *
  15. * @var string
  16. */
  17. protected $signature = 'sync:chapters {--bids=}';
  18. /**
  19. * The console command description.
  20. *
  21. * @var string
  22. */
  23. protected $description = '从内容平台同步章节';
  24. public function handle()
  25. {
  26. // 传参
  27. $bids = $this->option('bids');
  28. $bids = filterValidIds(explode(',', $bids));
  29. dLog('sync')->info('sync chapters start...');
  30. // 获取书籍列表
  31. $books = $this->getBooks($bids);
  32. // 执行更新
  33. $this->runSync($books);
  34. dLog('sync')->info('sync chapters end...');
  35. }
  36. /**
  37. * @param $bids
  38. * @return mixed
  39. */
  40. private function getBooks($bids)
  41. {
  42. // 组装查询条件
  43. $query = Book::select('id', 'zw_id')->where('zw_id', '>', 0);
  44. if ($bids) {
  45. $query->whereIn('id', $bids);
  46. } else {
  47. // 连载
  48. $query->where('status', 0);
  49. }
  50. // 查询
  51. return $query->orderBy('id')->get();
  52. }
  53. /**
  54. * @param $books
  55. * @return void
  56. */
  57. private function runSync($books): void
  58. {
  59. // 判空
  60. if ($books->isEmpty()) {
  61. return;
  62. }
  63. // 循环执行
  64. foreach ($books as $book) {
  65. // 这本书有点问题
  66. if (in_array($book->zw_id, [141873, 125717])) {
  67. continue;
  68. }
  69. dLog('sync')->info('sync book chapters start: ', [$book->id, $book->zw_id]);
  70. // 同步章节
  71. $this->syncChapters($book->id, $book->zw_id);
  72. dLog('sync')->info('sync book chapters end: ', [$book->id, $book->zw_id]);
  73. // 执行更新书籍数据脚本
  74. $this->call('book:after:spider', ['--bid' => $book->id]);
  75. }
  76. }
  77. /**
  78. * 制作新增章节的同步
  79. *
  80. * @param $bid
  81. * @param $zwBid
  82. * @return void
  83. */
  84. private function syncChapters($bid, $zwBid)
  85. {
  86. // 判空
  87. if (empty($bid) || empty($zwBid)) {
  88. return;
  89. }
  90. // 获取内容平台书籍所有章节列表
  91. $zwChapters = $this->getZwChapters($zwBid);
  92. if (empty($zwChapters)) {
  93. return;
  94. }
  95. // 获取抖音平台书籍所有章节(及内容)列表
  96. $chapters = Chapter::where('bid', $bid)->get();
  97. $chapterContents = ChapterContent::select('id', 'bid', 'zw_bid', 'zw_chapter_id')
  98. ->where('bid', $bid)
  99. ->where('zw_chapter_id', '>', 0)
  100. ->get();
  101. // 校验章节数据
  102. $diffData = $this->getDiffChapters($bid, $zwBid, $zwChapters, $chapters, $chapterContents);
  103. // 写入新章节
  104. $chaptersInData = getProp($diffData, 'chaptersInData', []);
  105. if ($chaptersInData) {
  106. Chapter::insert($chaptersInData);
  107. }
  108. // 更新章节
  109. $chaptersUpData = getProp($diffData, 'chaptersUpData', []);
  110. if ($chaptersInData) {
  111. $this->batchUpdateDb('chapters', $chaptersUpData);
  112. }
  113. // 写入新章节内容(内容数据较大,分批获取)
  114. $newZwChaptersContentIds = getProp($diffData, 'newZwChaptersContentIds', []);
  115. $zwChaptersContentData = getProp($diffData, 'zwChaptersContentData', []);
  116. $this->saveNewChaptersContent($newZwChaptersContentIds, $bid, $zwBid, $zwChaptersContentData);
  117. //更新章节的content_chapter_id
  118. $this->updateChaptersContentId($bid);
  119. }
  120. /**
  121. * 书籍章节比较
  122. *
  123. * @param $bid
  124. * @param $zwBid
  125. * @param $zwChapters
  126. * @param $chapters
  127. * @param $chapterContents
  128. * @return array
  129. */
  130. private function getDiffChapters($bid, $zwBid, $zwChapters, $chapters, $chapterContents): array
  131. {
  132. if (empty($zwChapters)) {
  133. return [];
  134. }
  135. // 循环章节数据
  136. $chaptersData = [];
  137. if ($chapters) {
  138. foreach ($chapters as $chapter) {
  139. $id = (int)getProp($chapter, 'id');
  140. $zwChapterId = (int)getProp($chapter, 'zw_chapter_id');
  141. // 当前已存在的章节
  142. $chaptersData[$zwChapterId] = ['id' => $id];
  143. }
  144. }
  145. // 循环章节内容表
  146. $chapterContentsData = [];
  147. if ($chapterContents) {
  148. foreach ($chapterContents as $chapterContent) {
  149. $bid = (int)getProp($chapterContent, 'bid');
  150. $zwChapterId = (int)getProp($chapterContent, 'zw_chapter_id');
  151. $zwBid = (int)getProp($chapterContent, 'zw_bid');
  152. // 当前已存在的章节内容
  153. $chapterContentsData[$zwChapterId] = ['bid' => $bid, 'zw_bid' => $zwBid];
  154. }
  155. }
  156. // 循环内容平台章节列表数据
  157. $now = date('Y-m-d H:i:s');
  158. $chaptersUpData = $chaptersInData = $newZwChaptersContentIds = $zwChaptersContentData = [];
  159. foreach ($zwChapters as $zwChapter) {
  160. // 内容平台章节id
  161. $zwChapterId = (int)getProp($zwChapter, 'id');
  162. $zwChapterContentId = (int)getProp($zwChapter, 'chapter_content_id');
  163. $name = getProp($zwChapter, 'name');
  164. // 章节基本数据,为了同步章节内容时用
  165. $zwChaptersContentData[$zwChapterContentId] = [
  166. 'bid' => $bid,
  167. 'chapter_name' => $name,
  168. 'zw_bid' => $zwBid,
  169. 'zw_chapter_id' => $zwChapterId,
  170. ];
  171. // 基本数据
  172. $item = [
  173. 'bid' => $bid,
  174. 'name' => $name,
  175. 'sequence' => (int)getProp($zwChapter, 'sequence'),
  176. 'size' => (int)getProp($zwChapter, 'size'),
  177. 'is_vip' => (int)getProp($zwChapter, 'is_vip'),
  178. 'is_check' => 1,
  179. 'is_deleted' => (int)getProp($zwChapter, 'is_deleted'),
  180. 'zw_bid' => $zwBid,
  181. 'zw_chapter_id' => $zwChapterId,
  182. 'recent_update_at' => $now,
  183. 'post_time' => $now,
  184. 'check_time' => $now,
  185. 'created_at' => $now,
  186. 'updated_at' => $now,
  187. ];
  188. // 区分章节写入还是更新
  189. if (!isset($chaptersData[$zwChapterId])) {
  190. $chaptersInData[] = $item;
  191. } else {
  192. // 去掉无需更新的字段
  193. unset($item['bid'], $item['zw_bid'], $item['zw_chapter_id'], $item['recent_update_at'],
  194. $item['post_time'], $item['check_time'], $item['created_at']);
  195. // 加上当前章节id
  196. $item['id'] = (int)getProp($chaptersData[$zwChapterId], 'id');
  197. $chaptersUpData[] = $item;
  198. }
  199. // 区分章节内容写入还是更新
  200. if (!isset($chapterContentsData[$zwChapterId])) {
  201. $newZwChaptersContentIds[] = $zwChapterContentId;
  202. }
  203. }
  204. return compact('chaptersUpData', 'chaptersInData', 'newZwChaptersContentIds', 'zwChaptersContentData');
  205. }
  206. /**
  207. * 写入章节内容
  208. *
  209. * @param $zwChaptersContentIds
  210. * @param $bid
  211. * @param $zwBid
  212. * @param $zwChaptersContentData
  213. * @return void
  214. */
  215. private function saveNewChaptersContent($zwChaptersContentIds, $bid, $zwBid, $zwChaptersContentData)
  216. {
  217. if (empty($zwChaptersContentIds)) {
  218. return;
  219. }
  220. // 当前时间
  221. $now = date('Y-m-d H:i:s');
  222. // 切分id
  223. $zwChapterContentIdsArr = array_chunk($zwChaptersContentIds, 100);
  224. // 循环切分数据
  225. foreach ($zwChapterContentIdsArr as $contentIds) {
  226. // 获取内容平台章节内容
  227. $zwChapterContents = $this->getZwChaptersContent($contentIds);
  228. if (empty($zwChapterContents)) {
  229. continue;
  230. }
  231. // 组装写入数据
  232. $chaptersContentInsertData = [];
  233. foreach ($zwChapterContents as $zwChapterContent) {
  234. $zwChapterContentId = (int)getProp($zwChapterContent, 'id');
  235. $zwChapterContentData = getProp($zwChaptersContentData, $zwChapterContentId, []);
  236. // 组装写入数据
  237. $chaptersContentInsertData[] = [
  238. 'bid' => $bid,
  239. 'zw_bid' => $zwBid,
  240. 'zw_chapter_id' => (int)getProp($zwChapterContentData, 'zw_chapter_id'),
  241. 'chapter_name' => getProp($zwChapterContentData, 'chapter_name'),
  242. 'content' => formatContent(getProp($zwChapterContent, 'content')),
  243. 'created_at' => $now,
  244. 'updated_at' => $now,
  245. ];
  246. }
  247. // 执行写入
  248. ChapterContent::insert($chaptersContentInsertData);
  249. }
  250. }
  251. /**
  252. * 更新章节chapter_content_id
  253. *
  254. * @param $bid
  255. * @return void
  256. */
  257. private function updateChaptersContentId($bid)
  258. {
  259. if (empty($bid)) {
  260. return;
  261. }
  262. // 获取新章节
  263. $chapters = $this->getChaptersByBid($bid);
  264. $chapterContents = $this->getChapterContentsByBid($bid);
  265. if (empty($chapters) || empty($chapterContents)) {
  266. return;
  267. }
  268. $chaptersData = $this->buildChaptersData($chapters);
  269. $chapterContentsData = $this->buildChaptersData($chapterContents);
  270. // 组装更新数据-更新章节表chapter_content_id
  271. $chaptersUpdateData = [];
  272. foreach ($chaptersData as $key => $value) {
  273. $chapterContent = getProp($chapterContentsData, $key, []);
  274. $chaptersUpdateData[] = [
  275. 'id' => getProp($value, 'id'),
  276. 'chapter_content_id' => (int)getProp($chapterContent, 'id'),
  277. ];
  278. }
  279. // 执行批量更新
  280. $this->batchUpdateDb('chapters', $chaptersUpdateData);
  281. }
  282. /**
  283. * @param $chapters
  284. * @return array
  285. */
  286. private function buildChaptersData($chapters): array
  287. {
  288. if (empty($chapters)) {
  289. return [];
  290. }
  291. $result = [];
  292. foreach ($chapters as $chapter) {
  293. $bid = getProp($chapter, 'bid');
  294. $zwBid = getProp($chapter, 'zw_bid');
  295. $zwChapterId = getProp($chapter, 'zw_chapter_id');
  296. $key = $bid . '_' . $zwBid . '_' . $zwChapterId;
  297. if (!isset($result[$key])) {
  298. $result[$key] = $chapter;
  299. }
  300. }
  301. return $result;
  302. }
  303. /**
  304. * @param $bid
  305. * @return array
  306. */
  307. private function getChaptersByBid($bid): array
  308. {
  309. if (empty($bid)) {
  310. return [];
  311. }
  312. $chapters = Chapter::select('id', 'bid', 'zw_bid', 'zw_chapter_id')->where('bid', $bid)->get();
  313. return $chapters ? $chapters->toArray() : [];
  314. }
  315. /**
  316. * @param $bid
  317. * @return array
  318. */
  319. private function getChapterContentsByBid($bid): array
  320. {
  321. if (empty($bid)) {
  322. return [];
  323. }
  324. $chapters = ChapterContent::select('id', 'bid', 'zw_bid', 'zw_chapter_id')->where('bid', $bid)->get();
  325. return $chapters ? $chapters->toArray() : [];
  326. }
  327. /**
  328. * 查询内容平台章节列表
  329. *
  330. * @param $bid
  331. * @return array
  332. */
  333. private function getZwChapters($bid): array
  334. {
  335. if (empty($bid)) {
  336. return [];
  337. }
  338. $result = DB::connection('zw_content_mysql')
  339. ->table('chapters')
  340. ->select('id', 'bid', 'name', 'sequence', 'size', 'is_vip', 'chapter_content_id', 'is_deleted', 'is_draft')
  341. ->where('bid', $bid)
  342. // ->where('is_draft', 0)
  343. // ->where('is_check', 1)
  344. // ->where('is_deleted', 0)
  345. ->orderBy('sequence')
  346. ->get();
  347. return $result ? $result->toArray() : [];
  348. }
  349. /**
  350. * @param $zwChapterContentIds
  351. * @return array
  352. */
  353. private function getZwChaptersContent($zwChapterContentIds): array
  354. {
  355. if (empty($zwChapterContentIds)) {
  356. return [];
  357. }
  358. $result = DB::connection('zw_content_mysql')
  359. ->table('chapter_contents')
  360. ->select('id', 'content')
  361. ->whereIn('id', $zwChapterContentIds)
  362. ->get();
  363. return $result ? $result->toArray() : [];
  364. }
  365. }