setName('kb:rebuild') ->setDescription('重建AI知识库文档') ->addOption('domain', null, Option::VALUE_OPTIONAL, 'article|post|lottery|match|all', 'all'); } protected function execute(Input $input, Output $output) { $domain = (string) $input->getOption('domain'); $targets = $domain === 'all' ? ['article', 'post', 'lottery', 'match'] : [$domain]; $summary = ['success' => 0, 'failed' => 0]; foreach ($targets as $target) { foreach ($this->yieldRows($target) as $row) { $result = KbService::upsertDocument($row['domain'], $row['subtype'], (int) $row['source_id']); if (!empty($result['success'])) { $summary['success']++; } else { $summary['failed']++; $output->writeln(sprintf( '[failed] %s/%s/%d %s', $row['domain'], $row['subtype'], $row['source_id'], $result['error'] ?? 'unknown' )); } } } KbSyncService::markLotteryBackfillJobs(); $output->writeln(sprintf('[kb:rebuild] success=%d failed=%d', $summary['success'], $summary['failed'])); return 0; } private function yieldRows(string $domain): \Generator { if ($domain === 'article') { yield from $this->yieldTableRows('article', 'article', 'article'); return; } if ($domain === 'post') { yield from $this->yieldTableRows('community_post', 'post', 'post'); return; } if ($domain === 'lottery') { yield from $this->yieldTableRows('lottery_draw_result', 'lottery', 'draw_result'); yield from $this->yieldTableRows('lottery_ai_analysis', 'lottery', 'ai_history'); return; } if ($domain === 'match') { yield from $this->yieldTableRows('match', 'match', 'event'); } } private function yieldTableRows(string $table, string $domain, string $subtype): \Generator { $lastId = 0; $chunkSize = 5000; while (true) { $ids = Db::name($table) ->where('id', '>', $lastId) ->order('id', 'asc') ->limit($chunkSize) ->column('id'); if (empty($ids)) { break; } foreach ($ids as $id) { $lastId = (int) $id; yield [ 'domain' => $domain, 'subtype' => $subtype, 'source_id' => $lastId, ]; } } } }