Files
sbnews/server/app/common/command/WorldcupArticleTranslate.php
2026-06-11 12:15:29 +08:00

130 lines
3.9 KiB
PHP

<?php
namespace app\common\command;
use app\api\logic\ArticleLogic;
use app\common\enum\YesNoEnum;
use app\common\model\article\Article;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class WorldcupArticleTranslate extends Command
{
protected function configure()
{
$this->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('<comment>[WorldcupTranslate] 没有找到可处理的世界杯文章</comment>');
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('<comment>[WorldcupTranslate] 翻译失败或无结果 #%d %s</comment>', $article['id'], $article['title'] ?? ''));
}
} catch (\Throwable $e) {
$failed++;
$output->writeln(sprintf('<error>[WorldcupTranslate] 异常 #%d %s - %s</error>', $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 [];
}
}