setName('worldcup_article_translate') ->setDescription('预热世界杯英文资讯翻译缓存'); } protected function execute(Input $input, Output $output) { $articles = Article::field('id,cid,title,desc,content,ext,is_show,delete_time') ->where('cid', 22) ->where('is_show', YesNoEnum::YES) // 与前台世界杯资讯列表保持一致,只预热用户最先看到的 50 条。 ->order('published_at', 'desc') ->order('id', 'desc') ->limit(50) ->select() ->toArray(); if (empty($articles)) { $output->writeln('[WorldcupTranslate] 没有找到可处理的世界杯文章'); return 0; } $translated = 0; $skippedExist = 0; $skippedNonEnglish = 0; $failed = 0; foreach ($articles as $article) { $ext = $this->decodeExt($article['ext'] ?? []); if (!empty($ext['translated_content'])) { $skippedExist++; continue; } if (!$this->isProbablyEnglish($article)) { $skippedNonEnglish++; continue; } try { $result = ArticleLogic::autoTranslateArticle($article); if ($result !== '') { $translated++; $output->writeln(sprintf('[WorldcupTranslate] 已翻译文章 #%d %s', $article['id'], $article['title'] ?? '')); } else { $failed++; $output->writeln(sprintf('[WorldcupTranslate] 翻译失败或无结果 #%d %s', $article['id'], $article['title'] ?? '')); } } catch (\Throwable $e) { $failed++; $output->writeln(sprintf('[WorldcupTranslate] 异常 #%d %s - %s', $article['id'], $article['title'] ?? '', $e->getMessage())); } } $output->writeln(sprintf( '[WorldcupTranslate] 完成,翻译 %d 条,已存在 %d 条,跳过非英文 %d 条,失败 %d 条', $translated, $skippedExist, $skippedNonEnglish, $failed )); return 0; } private function isProbablyEnglish(array $article): bool { $text = trim(implode(' ', [ (string) ($article['title'] ?? ''), (string) ($article['desc'] ?? ''), $this->normalizeText((string) ($article['content'] ?? '')), ])); if ($text === '') { return false; } if (preg_match('/[\x{4e00}-\x{9fff}]/u', $text)) { return false; } $letterCount = preg_match_all('/[A-Za-z]/', $text); $wordCount = preg_match_all('/\b[A-Za-z]{2,}\b/', $text); // 世界杯资讯里不少正文并不长,阈值过高会误杀正常英文稿件。 return $letterCount >= 30 && $wordCount >= 5; } private function normalizeText(string $content): string { if ($content === '') { return ''; } $content = strip_tags($content); $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $content = preg_replace('/\s+/u', ' ', $content); return trim($content); } private function decodeExt($ext): array { if (is_array($ext)) { return $ext; } if (is_string($ext) && $ext !== '') { $data = json_decode($ext, true); return is_array($data) ? $data : []; } return []; } }