import os, paramiko, json PW = os.environ["REMOTE_PASS"] c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect("207.57.129.228", port=19717, username="root", password=PW, timeout=15, allow_agent=False, look_for_keys=False) def run(cmd, t=120): si, so, se = c.exec_command(cmd, timeout=t) out = so.read().decode("utf-8", "replace") err = se.read().decode("utf-8", "replace") rc = so.channel.recv_exit_status() if out: print(out, end="") if err: print("[err]", err, end="", file=__import__("sys").stderr) return out # 1) 找带错误信息的文章(翻译状态虽然 ok 但字段里带"翻译失败"字样) print("--- 找出还残留错误标记的文章 ---") n = run("docker exec news-aggregator-postgres-1 psql -U news -d news -tA -c \"SELECT count(*) FROM articles WHERE title_zh LIKE '%[翻译失败:%' OR body_zh_text LIKE '%[翻译失败:%';\"") print(f" 残留错误文章数: {n.strip()}") # 2) 改回 pending print("\n--- 批量回退到 pending ---") run("docker exec news-aggregator-postgres-1 psql -U news -d news -c \"UPDATE articles SET translation_status = 'pending', title_zh = NULL, body_zh_text = NULL, body_zh_html = NULL WHERE title_zh LIKE '%[翻译失败:%' OR body_zh_text LIKE '%[翻译失败:%';\" 2>&1 | tail -3") # 3) 触发 worker 翻译 print("\n--- 触发翻译(120s 等待)---") run("cd /srv/news && docker exec news-aggregator-worker-1 python -c 'import asyncio; from app.workers.pipeline import _translate_recent_for_source; async def t(): [await _translate_recent_for_source(sid, max_n=300) for sid in [2,3,4,5]]; asyncio.run(t())' 2>&1 | tail -10", t=180) import time time.sleep(10) # 4) 翻译后统计 print("\n--- 翻译后统计 ---") run("docker exec news-aggregator-postgres-1 psql -U news -d news -c \"SELECT translation_status, translation_engine, count(*), sum(translation_chars) FROM articles GROUP BY 1, 2 ORDER BY 1, 2;\"") # 5) 看一条 BBC 详情 print("\n--- BBC 详情 ---") out = run("curl -s -X POST http://localhost/api/v1/auth/login -H 'Content-Type: application/json' -d '{\"username\":\"owner\",\"password\":\"Owner2026!\"}'") token = json.loads(out)["access_token"] out = run(f"curl -s -H 'Authorization: Bearer {token}' 'http://localhost/api/v1/articles?source=bbc-world&limit=1'") ad = json.loads(out) if ad.get("items"): aid = ad["items"][0]["id"] out = run(f"curl -s -H 'Authorization: Bearer {token}' 'http://localhost/api/v1/articles/{aid}'") try: det = json.loads(out) print(f"\n=== {det['source']['name']} #{aid} ===") print(f" title: {det['title'][:80]}") print(f" title_zh: {(det.get('title_zh') or '—')[:80]}") print(f" body_text 长度: {len(det['body_text'])}") print(f" body_zh_text 长度: {len(det.get('body_zh_text') or '')}") if det.get("body_zh_text"): print(f"\n 译文(前 600 字符):") print(f" {det['body_zh_text'][:600]}") except Exception as e: print(f" err: {e}\n raw: {out[:200]}") # 6) /me/usage u = json.loads(run(f"curl -s -H 'Authorization: Bearer {token}' 'http://localhost/api/v1/me/usage'")) print(f"\n--- /me/usage ---\n {u}") c.close()