diff --git a/README.md b/README.md index 64912c3..ab5bc56 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,625 @@ -# Diary News · 私人新闻汇总系统 +# Diary News · 私人多源新闻翻译系统 -> 抓取境外权威源 → 自动翻译 → 网页 + Android 双端展示。 -> 跑在一台 30G 香港 VPS 上,自用 + 家人/小圈子。 +> 抓境外权威源 → 自动中英对照 → 智能排版/分类/插图/点评。 +> 跑在一台 2C/2G/30G 的香港 VPS 上,自用 + 家人/小圈子。 -完整方案见 [`docs/architecture.md`](./docs/architecture.md),部署步骤见 [`DEPLOY.md`](./DEPLOY.md)。 +--- + +## 目录 + +1. [项目目标](#项目目标) +2. [关键特性](#关键特性) +3. [架构概览](#架构概览) +4. [技术栈](#技术栈) +5. [仓库结构](#仓库结构) +6. [数据模型](#数据模型) +7. [快速开始](#快速开始) +8. [功能详解](#功能详解) +9. [LLM 智能增强](#llm-智能增强) +10. [API 概览](#api-概览) +11. [开发-部署工作流](#开发-部署工作流) +12. [运维工具](#运维工具) +13. [故障排查](#故障排查) +14. [路线图](#路线图) + +--- + +## 项目目标 + +**Why 存在**: + +| 痛点 | 解决 | +|---|---| +| 信息茧房(算法推荐让你只看一类) | 多源并列,无个性化排序,纯时间序 | +| 翻译门槛(英文看不动) | 自动翻译(腾讯云 TMT + 本地 NLLB 降级) | +| 内容保存难(网页 404、推文删) | 抓取入库 + 全文 + 译文,永久可查 | +| 单台服务器成本敏感 | 30G 跑全栈,月费 ≤ 50 HKD | + +**Who 给谁用**: + +- **owner**: 唯一管理员,管理源 / 提示词 / 看健康看板 +- **member**: 家庭成员 / 朋友,登录看文章 / 收藏 / 订阅关键词 + +--- + +## 关键特性 + +- 🌍 **多源 RSS 抓取**:Reuters / BBC / Al Jazeera / NHK / DW,带失败退避(连续 3 次失败把间隔 × 2,封顶 12 小时) +- 🌐 **智能翻译**:腾讯云 TMT(月 500 万字符配额)→ 本地 NLLB-200 降级,30 天 Redis 缓存避免重复 +- 🤖 **LLM 智能增强** *(新)*:翻译完成后自动跑 4 项 LLM 任务 — 排版 / 分类 / 插图 / 点评 +- 🎨 **AI 配图**:文生图模型自动为每篇文章生成插图(走 Agnes 平台,带限速) +- 👤 **双角色鉴权**:JWT(access 60min + refresh 14d) + API Token(sha256,可撤销,给 Android 预留) +- 📌 **收藏 + 关键词订阅**:用户级书签,服务端定时按关键词命中推送(预留 Telegram 通道) +- 📊 **管理看板**:源健康度 / 翻译配额 / LLM 状态,全部可视化 +- 🔄 **热加载**:源/提示词改了不用重启,worker 每天 00:30 重建 job +- 🚀 **一键部署**:SSH 推公钥 + 一键 `git pull` 流程 +- 🔒 **安全默认**:bcrypt 密码、API Token 加密、SQL 注入免疫(SQLAlchemy 2.0 参数化) + +--- + +## 架构概览 + +``` +┌─────────────────────────────────────────────────────────┐ +│ VPS (HK-News · 2C/2G/30G · Ubuntu 24) │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ postgres │ │ redis │ │ caddy │ ← 唯一 │ +│ └──────────┘ └──────────┘ │ :80/443 │ 对外 │ +│ ▲ ▲ └────┬─────┘ │ +│ │ │ │ │ +│ ┌────┴──────┐ ┌────┴────┐ ┌────────┴───────┐ │ +│ │ api │ │ worker │ │ frontend │ │ +│ │ FastAPI │ │APSch+任务│ │ (nginx + SPA)│ │ +│ └────┬──────┘ └────┬────┘ └────────────────┘ │ +│ │ │ │ +│ └──────┬───────┘ │ +│ │ │ +│ ┌───────────▼──────────┐ │ +│ │ RSS 抓取(feedparser)│ │ +│ │ 翻译(Tencent+NLLB) │ │ +│ │ LLM 增强(Agnes) │ ← 排版/分类/插图/点评 │ +│ │ url_hash 去重 │ │ +│ └──────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ + │ + │ HTTPS / Push / Pull + ▼ + ┌──────────────────────┐ + │ Gitea(代码托管) │ + │ http://...:3000 │ + └──────────────────────┘ +``` + +**数据流**(单篇文章的一生): +``` +RSS Feed → feedparser → FetchedItem + ↓ +url_hash = SHA1(url) + ON CONFLICT DO NOTHING ← 去重 + ↓ (新文章入库,translation_status=pending) + ↓ +[translation_loop] 1篇/秒 Semaphore(1) + ↓ 调 腾讯 TMT → body_zh_text/html + ↓ status: pending → ok + ↓ +[enrichment_loop] 扫描 *_status=pending 的已译文章 + ↓ 调 Agnes LLM: 排版 → 分类 → 插图 → 点评 + ↓ 4 任务独立 try/except,共享 chat_sem + image_sem 限速 + ↓ status: ok / failed + ↓ +[文章详情接口] 原文 + 译文 + AI 排版版 + 分类 + 插图 + 点评 全部展示 +``` + +--- + +## 技术栈 + +### 后端 + +| 层 | 选型 | +|---|---| +| 语言 | Python 3.12 | +| Web 框架 | FastAPI 0.115 + Uvicorn | +| ORM | SQLAlchemy 2.0(asyncio)+ Alembic | +| 数据库 | PostgreSQL 16(asyncpg + psycopg2) | +| 缓存/限速 | Redis 7(256MB LRU) | +| HTTP 客户端 | httpx 0.28(异步) | +| RSS 解析 | feedparser 6 | +| HTML 抽取 | trafilatura 2 + BeautifulSoup4 + lxml | +| 翻译主 | 腾讯云 TMT(SDK: tencentcloud-sdk-python) | +| 翻译降级 | transformers + NLLB-200-distilled-600M | +| LLM | Agnes(OpenAI 兼容): agnes-2.0-flash / agnes-image-2.1-flash | +| 调度 | APScheduler 3.10(AsyncIO + Cron + Interval) | +| 鉴权 | passlib[bcrypt] 1.7 + bcrypt 4.0.1 + PyJWT 2.10 | + +### 前端 + +| 层 | 选型 | +|---|---| +| 框架 | Vue 3.5 + Vite 5 | +| UI 库 | Naive UI 2.40 | +| 状态 | Pinia 2.2 | +| 路由 | vue-router 4.4 | +| HTTP | axios 1.7(自动 401 refresh) | +| 时间 | dayjs 1.11 | + +### 部署 + +| 层 | 选型 | +|---|---| +| 容器化 | Docker Compose(7 服务) | +| 反代 | Caddy 2(alpine) | +| 静态 | nginx 1.27-alpine(SPA fallback) | +| 构建 | pyproject.toml(setuptools) + npm | + +--- ## 仓库结构 ``` diary-news/ -├── backend/ # FastAPI 后端 + worker + scheduler +├── backend/ # FastAPI 后端 │ ├── app/ -│ │ ├── api/ # 路由 -│ │ ├── core/ # 安全 / 依赖 -│ │ ├── models/ # SQLAlchemy 模型 -│ │ ├── schemas/ # Pydantic schemas -│ │ ├── services/ # 采集 / 翻译 -│ │ ├── workers/ # 抓取 / 翻译 pipeline + APScheduler -│ │ ├── scripts/ # 初始化脚本 -│ │ ├── config.py # Pydantic Settings -│ │ ├── database.py # 异步 SQLAlchemy -│ │ └── main.py # FastAPI 入口 -│ ├── alembic/ # 迁移 +│ │ ├── api/ # HTTP 路由 +│ │ │ ├── auth.py # /auth/login, /auth/refresh +│ │ │ ├── me.py # /me, /me/usage(翻译配额) +│ │ │ ├── articles.py # /articles 列表 + 详情 + 游标分页 +│ │ │ ├── sources.py # /sources 只读列表 +│ │ │ ├── bookmarks.py # /bookmarks 收藏 +│ │ │ ├── subscriptions.py # /subscriptions 关键词订阅 +│ │ │ ├── admin.py # /admin/sources, /admin/health, /admin/refresh +│ │ │ └── admin_llm.py # /admin/llm/settings, /admin/llm/enrich/{id} +│ │ ├── core/ +│ │ │ ├── security.py # bcrypt + JWT + API Token +│ │ │ └── deps.py # get_current_user, require_owner +│ │ ├── models/ # SQLAlchemy 2.0 ORM +│ │ │ ├── user.py +│ │ │ ├── source.py +│ │ │ ├── article.py +│ │ │ ├── bookmark.py +│ │ │ ├── subscription.py +│ │ │ ├── api_token.py +│ │ │ └── llm_setting.py # LLM 设置(单行表) +│ │ ├── schemas/ # Pydantic v2 I/O 模型 +│ │ │ ├── auth.py +│ │ │ ├── article.py +│ │ │ ├── source.py +│ │ │ ├── llm.py # LLM 设置 schemas + 默认提示词 +│ │ │ └── misc.py +│ │ ├── services/ +│ │ │ ├── fetchers/ # RSS / HTML / Telegram 采集器 +│ │ │ ├── translation/ # 腾讯 TMT + 本地 NLLB + 配额门面 +│ │ │ └── llm/ # Agnes client + 智能增强 +│ │ │ ├── client.py +│ │ │ └── enrichment.py +│ │ ├── workers/ +│ │ │ ├── __main__.py # APScheduler + translation_loop + enrichment_loop +│ │ │ └── pipeline.py # fetch_one_source / translate_article +│ │ ├── scripts/ # 初始化(create_user / seed_sources) +│ │ ├── config.py # Pydantic Settings(从 .env 读) +│ │ ├── database.py # 异步 SQLAlchemy 引擎 +│ │ ├── redis_client.py # Redis 单例 +│ │ └── main.py # FastAPI 入口 +│ ├── alembic/ +│ │ ├── env.py +│ │ ├── versions/ +│ │ │ ├── 0001_initial.py # 6 张表 + 枚举 +│ │ │ └── 0002_llm_settings_and_articles_ai.py +│ │ └── alembic.ini +│ ├── Dockerfile # python:3.12-slim +│ ├── pyproject.toml +│ └── .env.example +│ +├── frontend/ # Vue 3 + Vite + Naive UI +│ ├── src/ +│ │ ├── main.ts # createApp + Pinia + Router +│ │ ├── App.vue +│ │ ├── router.ts # 路由守卫(requiresAuth / ownerOnly) +│ │ ├── api/ +│ │ │ ├── client.ts # axios + 401 refresh 拦截器 +│ │ │ └── articles.ts # articles / sources / me / bookmarks / admin +│ │ ├── stores/auth.ts # Pinia 鉴权状态(localStorage 持久化) +│ │ ├── components/ +│ │ │ └── AppLayout.vue # 顶栏 + 侧栏 + 配额条 +│ │ └── views/ +│ │ ├── Login.vue +│ │ ├── Feed.vue # 24h 列表 + 游标分页 +│ │ ├── ArticleDetail.vue # 原文/译文/AI 排版/分类/插图/点评 +│ │ ├── Bookmarks.vue +│ │ ├── Sources.vue +│ │ ├── AdminSources.vue # owner: 源管理 CRUD +│ │ └── AdminLlmSettings.vue # owner: LLM 提示词 + 测连接 + 触发 │ ├── Dockerfile -│ └── pyproject.toml -├── frontend/ # Vue 3 + Vite + Naive UI +│ ├── nginx.conf +│ ├── vite.config.ts +│ ├── tsconfig.json +│ └── package.json +│ ├── docs/ -│ └── architecture.md -├── Caddyfile # 反代 -├── docker-compose.yml -├── .env.example -├── DEPLOY.md -└── README.md +│ ├── architecture.md # 77 行实现版架构 +│ └── acceptance.md # MVP 验收清单 +│ +├── scripts/ # 工具脚本 +│ ├── deploy_pull.py # 免密部署:clone/pull + 失败回滚 +│ ├── server_init.py # 远程服务器初始化(推公钥 + 7 项系统级运维) +│ ├── push_ssh_key.py # 单推公钥(已用 fingerprint 去重) +│ ├── _*.py # 临时调试脚本(gitignored) +│ └── deploy_remote.sh # 远程一键部署(老脚本) +│ +├── .env.example # 配置示例(可 commit) +├── .gitignore # 忽略 .env / scripts/_*.py / node_modules 等 +├── Caddyfile # 反代规则 +├── docker-compose.yml # 7 服务 +├── DEPLOY.md # 完整部署手册(165 行) +├── README.md # 本文件 +└── news-aggregator-plan.md # 613 行方案设计 v0.1(决策背景) ``` -## 快速开始(本地开发) +--- + +## 数据模型 + +6 张表 + 1 张配置表(全部 PostgreSQL): + +| 表 | 关键字段 | 说明 | +|---|---|---| +| **users** | role(enum: owner/member), password_hash | 用户 + 角色 | +| **sources** | slug(uniq), kind(rss/html_list/tg_channel), priority, fetch_interval_min, consecutive_failures | 采集源 | +| **articles** | url_hash(uniq), translation_status(pending/ok/partial/failed), category, commentary, body_zh_formatted, image_ai_url, *_status, category, entities(JSONB), sentiment, topic_id, bias | 文章 + 译文 + LLM 增强 | +| **bookmarks** | (user_id, article_id) UNIQUE | 收藏 | +| **subscriptions** | keyword, match_in(any/title/body), channel | 关键词订阅 | +| **api_tokens** | token_hash(sha256), expires_at, revoked_at | Android 预留 | +| **llm_settings** | format_prompt, classify_prompt, commentary_prompt, image_prompt_template, image_size, chat_model, image_model, interval_sec, enabled | LLM 提示词(单行) | + +ER 关系: +- `users` 1:N → `bookmarks` / `subscriptions` / `api_tokens` +- `sources` 1:N → `articles`(cascade delete) +- `articles` 1:N → `bookmarks`, self-ref `duplicate_of`(去重链) + +--- + +## 快速开始 + +### 本地开发(Linux/Mac/WSL2) ```bash -# 1. 准备环境 +# 1. 克隆 +git clone http:///xiaji/diary-news.git +cd diary-news + +# 2. 配置 cp .env.example .env -# 编辑 .env 填入密钥 +# 编辑 .env 填入: +# POSTGRES_PASSWORD / REDIS_PASSWORD / JWT_SECRET(用 openssl rand -hex) +# TENCENTCLOUD_SECRET_ID / TENCENTCLOUD_SECRET_KEY +# AGNES_API_KEY -# 2. 启动 -docker compose up -d +# 3. 启动 +docker compose up -d --build -# 3. 初始化数据库 + 创建 owner 账号 + 导入 5 个种子源 +# 4. 初始化 docker compose exec api alembic upgrade head docker compose exec api python -m app.scripts.create_user --username owner --password YOUR_PASS docker compose exec api python -m app.scripts.seed_sources -# 4. 触发一次抓取(看效果) +# 5. 触发一次抓取 docker compose exec api python -c "import asyncio; from app.workers.pipeline import run_once; asyncio.run(run_once())" -# 5. 打开 -# http://localhost/ +# 6. 打开 +open http://localhost/ # macOS +xdg-open http://localhost/ # Linux ``` +### Windows 本机(无 WSL) + +直接装 Python 3.12 + Node 20: + +```powershell +# 后端 +cd backend +py -3.12 -m venv .venv +.venv\Scripts\activate +pip install -e . +# 起 postgres / redis(用 docker run -d 或者本机服务) +$env:DATABASE_URL = 'postgresql+asyncpg://...' +alembic upgrade head +uvicorn app.main:app --reload + +# 前端 +cd ../frontend +npm install +npm run dev +``` + +### 远程部署 + +完整步骤见 [`DEPLOY.md`](./DEPLOY.md)。**已部署过一次的机器**只需 2 步: + +```bash +# 1. 在本机 push 代码 + 跑 deploy_pull.py +git push origin main +python scripts\deploy_pull.py # 自动 clone/pull + 失败回滚 + +# 2. 在服务器上重启应用(代码变了) +ssh hknews +cd /root/diary-news +docker compose restart api worker +docker compose exec api alembic upgrade head +``` + +--- + +## 功能详解 + +### RSS 抓取 + +- **5 个种子源**:Reuters / BBC / Al Jazeera / NHK / DW +- **抓取频率**:每源 60 分钟(可调),失败连续 3 次后间隔 × 2(封顶 12 小时) +- **去重**:`url_hash = SHA1(url)`,PG `ON CONFLICT DO NOTHING` 幂等 +- **HTML 抽取**:trafilatura 抓全文,RSS 摘要短时自动补抓 +- **可见性**:`/admin/sources` + `/admin/health` 看板 + +### 翻译(配额 + 缓存 + 降级) + +``` +[译文不存在] + ↓ Semaphore(1) — 1 篇/秒 + ↓ +[缓存命中?] + ├─ 是 → 直接返回(30 天有效) + └─ 否 ↓ +[主引擎(腾讯 TMT)可配额?] + ├─ 是 → 调 TMT → 写缓存 + 加计数 + └─ 否 ↓ +[本地 NLLB 启用?] + ├─ 是 → 调 NLLB → 写缓存 + └─ 否 → 原文 + [本条未翻译:配额耗尽] 标记 +``` + +配额在 Redis `translation:month:YYYYMM` 计数器(月度自动滚动)。`TENCENT_TMT_QUOTA_BUFFER=0.05` 表示 95% 触发后切本地(避免爆配额)。 + +### 用户 / 鉴权 + +- **JWT**:access 60min + refresh 14d,HS256 签名 +- **API Token**:Android 客户端用,sha256 存储(可撤销 + 过期) +- **角色**:`owner` 全部权限,`member` 看文章/收藏/订阅 +- **密码**:bcrypt 4.0.1(锁版兼容 passlib) + +### 收藏 / 关键词订阅 + +- 收藏:点星星,加到 `bookmarks`,有 note 字段 +- 关键词订阅:扫 `articles.body_text`/`title`,命中后写入 `subscriptions.last_hit_at`,预留 Telegram 通道(MVP 不发) + +--- + +## LLM 智能增强 + +> **新功能**(2026-06-08 加入)。翻译完成后,自动调 Agnes LLM 跑 4 项独立任务。 + +### 4 项任务 + +| 任务 | 输出字段 | LLM 类型 | 用途 | +|---|---|---|---| +| **排版** | `articles.body_zh_formatted` | chat | 重写译文为网页排版(分段/加粗/列表) | +| **分类** | `articles.category` | chat(返 JSON) | 给文章打 1-3 个分类标签 | +| **插图** | `articles.image_ai_url` | image | 文生图,英文 prompt 拼自 `title` | +| **点评** | `articles.commentary` | chat | 100-200 字评论,客观有深度 | + +### 限速 + +- 单一 `LlmClient`,内部 `chat_sem` + `image_sem` 各 1 个并发 +- 每次调用后 `await asyncio.sleep(LLM_INTERVAL_SEC)`(默认 2.0s) +- chat 和 image 互不阻塞 +- 4 个任务在 `enrich_article` 内**串行**(已过 client 限速) + +### 设置页(Owner only) + +`/admin/llm` → 4 个 textarea + 几个 input: + +- **总开关** `enabled` +- **4 个提示词**(可重置默认):`format_prompt` / `classify_prompt` / `commentary_prompt` / `image_prompt_template` +- **模型**:`chat_model` / `image_model`(默认 agnes-2.0-flash / agnes-image-2.1-flash) +- **插图尺寸**:`image_size`(默认 1024x768) +- **限速**:`interval_sec`(默认 2.0) +- **测连接**:发个 `ping` chat 请求,1-2 秒内返 OK +- **手动触发**:`POST /admin/llm/enrich/{article_id}` 跑一篇 4 任务 + +### 默认提示词 + +`backend/app/schemas/llm.py` 的 `DEFAULT_PROMPTS`,支持占位符: +- `{body}` — 译文正文 +- `{title}` — 译后标题 +- `{summary}` — 摘要 + +### 失败隔离 + +每个任务独立 try/except,失败标 `*_status='failed'`,**不影响**其他任务。 +`enrichment_loop` 扫 `*_status` 是 `pending/failed/n/a` 的文章,自动重试 failed。 + +### 历史文章批量 enrich + +新功能**只对**翻译完成后入库的文章生效。历史已翻译文章,手动 reset: + +```sql +UPDATE articles +SET format_status='pending', classify_status='pending', + image_ai_status='pending', commentary_status='pending' +WHERE translation_status='ok'; +``` + +--- + +## API 概览 + +所有 API 在 `/api/v1` 前缀下,完整定义见 `http:///api/docs`(DEBUG 模式)。 + +### 公开 + +- `POST /auth/login` — 用户名 + 密码 → access + refresh token +- `POST /auth/refresh` — refresh token → 新 access + +### 需要登录(任意角色) + +- `GET /me` — 当前用户信息 +- `GET /me/usage` — 翻译配额(已用 / 总额 / 百分比) +- `GET /articles?since=...&source=...&q=...&cursor=...` — 列表(游标分页) +- `GET /articles/{id}` — 详情 +- `GET /sources` — 源列表(只读) +- `GET /bookmarks` / `POST /bookmarks` / `DELETE /bookmarks/{id}` +- `GET /subscriptions` / `POST /subscriptions` / `DELETE /subscriptions/{id}` + +### Owner only(`/admin/*`) + +- `GET /admin/sources` / `POST` / `PATCH /{id}` / `DELETE /{id}` — 源 CRUD +- `POST /admin/refresh/{source_id}` — 立即触发抓取 +- `POST /admin/translation/rerun/{article_id}` — 重译 +- `GET /admin/health` — 源健康看板 +- `POST /admin/translation/quota/reset` — 重置本月配额 +- `GET /admin/llm/settings` / `PUT` / `POST /reset` / `POST /test` — LLM 设置 +- `POST /admin/llm/enrich/{article_id}` — 手动触发某篇 enrich + +--- + +## 开发-部署工作流 + +``` +┌─────────────────────────────────────┐ +│ 本地 Windows (你) │ +│ ─ 编辑代码 │ +│ ─ git add / commit │ +│ ─ git push origin main │ +└─────────────────┬───────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ Gitea(代码托管) │ +└─────────────────┬───────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 跑 python scripts\deploy_pull.py │ +│ ─ 免密登录(SSH key) │ +│ ─ git clone/pull │ +│ ─ 成功:保持 + 报告 │ +│ ─ 失败:git reset --hard <前 sha> │ +└─────────────────┬───────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 远程 HK-News │ +│ ─ 代码最新 │ +│ ─ (如果需要)docker compose restart│ +│ ─ (如果需要)alembic upgrade head │ +└─────────────────────────────────────┘ +``` + +**完整循环命令**(本机跑): + +```bash +git add -A +git commit -m "feat: ..." +git push origin main +python scripts\deploy_pull.py # 我帮你跑,你说"拉一下"即可 +``` + +--- + +## 运维工具 + +| 脚本 | 用途 | 调用 | +|---|---|---| +| `scripts/deploy_pull.py` | 免密拉取 + 失败回滚 | `python scripts\deploy_pull.py` | +| `scripts/server_init.py` | 服务器系统级初始化(推公钥 + 7 项运维) | `REMOTE_PASS=xxx python scripts/server_init.py` | +| `scripts/push_ssh_key.py` | 单推 SSH 公钥(SSH key fingerprint 去重) | `REMOTE_PASS=xxx python scripts/push_ssh_key.py` | +| `docker compose logs -f worker` | 看 worker 日志 | 服务器上 | +| `docker compose exec api alembic upgrade head` | 跑 migration | 服务器上 | + +### deploy_pull.py 完整参数 + +```bash +python scripts\deploy_pull.py \ + --host 207.57.129.228 \ + --port 19717 \ + --user root \ + --repo-dir /root/diary-news \ + --repo-url http://124.223.26.33:3000/xiaji/diary-news.git \ + --branch main + +# 干跑 +python scripts\deploy_pull.py --dry-run + +# 手动回退 +python scripts\deploy_pull.py --rollback +``` + +也支持 env var:`DEPLOY_HOST` / `DEPLOY_PORT` / `DEPLOY_USER` / `DEPLOY_REPO_DIR` / `DEPLOY_REPO_URL` / `DEPLOY_SSH_KEY`。 + +--- + +## 故障排查 + +### Q: 翻译一直失败? +1. `docker compose logs worker | grep -E "translate|tencent"` +2. 看 `translation:month:YYYYMM` Redis key 是不是满了 +3. 调 `.env` 的 `TENCENT_TMT_QUOTA_BUFFER=0.1` 给更多缓冲 + +### Q: 某个 RSS 源一直 fail? +1. `/admin/health` 看 `consecutive_failures` 字段 +2. `docker compose logs worker | grep ` +3. 大概率是 RSS URL 失效或被反爬,先 `enabled=false` 暂停,在 `/admin/sources` 编辑后重启 worker + +### Q: LLM 增强不工作? +1. `/admin/llm` → 点 "测连接" +2. 看后端日志 `docker compose logs worker | grep -E "enrich|chat"` +3. 确认 `.env` 里有 `AGNES_API_KEY` + +### Q: 服务器磁盘快满? +```sql +DELETE FROM articles WHERE published_at < now() - interval '90 day' AND duplicate_of IS NULL; +``` + +### Q: deploy 失败,想回退? +```bash +python scripts\deploy_pull.py --rollback <之前的好 sha> +# 或者手动 +ssh hknews "cd /root/diary-news && git reset --hard " +``` + +### Q: 中文用户名乱码? +PowerShell 默认 GBK,运行前先 `chcp 65001` 切 UTF-8。 + +--- + +## 路线图 + +- [x] **Phase 1 (MVP)**:5 RSS 源 + 翻译 + 网页 + admin CRUD +- [x] **Phase 1.5**:LLM 智能增强(排版/分类/插图/点评) ✅ 2026-06-08 +- [ ] **Phase 2**:PWA 离线缓存 / 关键词订阅推送(Telegram) +- [ ] **Phase 3**:Android 客户端(API Token 已预留) +- [ ] **Phase 4**:自动分类/点评/实体识别(目前是 LLM 一次性,无 ML pipeline) +- [ ] **Phase 5**:跨源立场对照 / 主题聚类 + +--- + +## 文档导航 + +- 📄 [DEPLOY.md](./DEPLOY.md) — 完整部署手册(新机器从 0 到能访问) +- 📐 [docs/architecture.md](./docs/architecture.md) — 实现版架构(77 行) +- ✅ [docs/acceptance.md](./docs/acceptance.md) — MVP 验收清单 +- 📜 [news-aggregator-plan.md](./news-aggregator-plan.md) — 613 行方案设计 v0.1(选型决策背景) +- 🛠 [scripts/deploy_pull.py](./scripts/deploy_pull.py) — 部署工具源码 + +--- + ## 设计原则 - **轻量**:单机 30G 能跑,不堆重型服务 -- **可控**:源管理 / 翻译配额 / 抓取调度全部可视化 -- **可扩展**:ML 字段已建好(分类/点评/实体),不需改表 +- **可控**:源管理 / 翻译配额 / 抓取调度 / LLM 提示词 全部可视化 +- **可扩展**:ML 字段(category/commentary/entities/sentiment/bias)已建好,后续直接写值不动表 - **不反爬对抗**:愿意被 ban IP 就 ban,优先合规 +- **透明失败**:不静默吞错,每个 stage 都有 `*_status` 字段记录 +- **幂等可重跑**:所有运维脚本(server_init / deploy_pull)都幂等,跑多遍无副作用 -## 当前阶段 +--- -**Phase 1 · MVP(本仓库)** -- ✅ 5 个权威 RSS 源采集(Reuters/BBC/Al Jazeera/NHK/DW) -- ✅ 腾讯云 TMT 翻译 + 字符配额监控 + 降级 -- ✅ 网页:登录 / 24h 列表 / 详情 / 源管理 -- ✅ 凌晨分波次调度 -- ⏳ Android(Phase 3) -- ⏳ 智能分类/点评(Phase 4) -- ⏳ PWA 离线 / 推送(Phase 2) +**License**: Private use only.