Files
diary-news/news-aggregator-plan.md
Mavis 60b062daf2 feat: initial MVP - FastAPI backend + Vue3 frontend + docker-compose
- backend: FastAPI + SQLAlchemy 2.0(async) + asyncpg + Alembic
- 7 API routes: auth/me/articles/sources/bookmarks/subscriptions/admin
- models: User/Source/Article/Bookmark/Subscription/ApiToken
- services: RSS fetcher (feedparser) + Tencent TMT translator with quota + cache + local NLLB fallback
- workers: APScheduler + asyncio pipeline (fetch -> dedupe -> insert -> translate)
- seed scripts: create_user, seed_sources (5 RSS: Reuters/BBC/Al Jazeera/NHK/DW)
- frontend: Vue 3 + Vite + Naive UI + Pinia + vue-router
- pages: Login, Feed (24h), ArticleDetail, Sources, Bookmarks, AdminSources
- deploy: docker-compose (postgres/redis/api/worker/frontend/caddy)
- docs: README, DEPLOY, architecture, acceptance
2026-06-07 21:51:01 +08:00

614 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 私人新闻汇总系统 · 方案设计 v0.1
> 适用环境:香港云服务器 / Ubuntu 24.04 LTS / Intel E5·Platinum / 30G SSD / IP 直访
> 目标受众:自己 + 家人/小圈子(2~10 人)
> 设计原则:**轻量、可控、可扩展、不被反爬干掉**
---
## 0. TL;DR(一页版)
- **架构**:前后端分离 + 单一 API 网关,采集 → 入库 → 翻译 → API → 前后端展示。
- **存储**:PostgreSQL 主库 + 本地文件(原文/图片)+ Redis 缓存。**30G SSD 是硬约束**,所以图片默认只存外链,正文做"近 30 天热保留 + 冷归档"两段式。
- **采集**:凌晨分波次拉取,每源独立 cron 表达式;RSS 走 `feedparser`,非 RSS 走 `trafilatura`/`playwright`
- **翻译**:腾讯云为主(500 万字符配额),超额走本地 `LibreTranslate`/`NLLB`;`title/summary/正文` 分块,字符用滑动窗口月度计数。
- **展示**:网页默认"过去 24h"瀑布流(原文 + 译文并列),Android(Kotlin · Jetpack Compose)走同套 API。
- **预留**:`category` / `commentary` / `entities` / `sentiment` 字段先建好,模型后插。
---
## 1. 系统总览
### 1.1 业务目标(为什么做)
1. **破除信息茧房**:同源 + 异源对照,可选集成 Ground News 立场标记。
2. **抗审查 / 抗算法推荐**:原始列表流,不做兴趣推荐(至少 MVP 不做)。
3. **可读可搜可归档**:私有领域做"个人情报库",不是又一个今日头条。
4. **成本可控**:跑在一台 30G 香港 VPS 上,月费用 ≤ 50 HKD。
### 1.2 非目标(明确不做)
- ❌ 不做内容创作/UGC
- ❌ 不做用户关注关系、社交
- ❌ MVP 不做推荐/个性化(后续可选)
- ❌ 不做 iOS 端(用户明确)
- ❌ 不做爬虫对抗到极致(愿意被 ban IP 就 ban)
### 1.3 用户与权限
| 角色 | 权限 |
| --- | --- |
| Owner(你) | 全部 + 源管理 + 翻译配额监控 + 用户管理 |
| Member(家人/朋友) | 只读 + 收藏 + 关键词订阅 |
| Guest(可后续加) | 只读 + 24h 滑窗 |
鉴权用 JWT + HTTP-Only Cookie(网页)/ Bearer Token(APP),密码 bcrypt。
---
## 2. 整体架构
### 2.1 分层(逻辑视图)
```
┌──────────────────────────────────────────────────────────┐
│ 表现层(Presentation) │
│ ├─ Web (Vue 3 / Vite, PWA, 自适应) │
│ └─ Android (Kotlin / Jetpack Compose, Material 3) │
└──────────────────┬───────────────────────────────────────┘
│ HTTPS / JSON (统一 API)
┌──────────────────▼───────────────────────────────────────┐
│ API 网关层 (FastAPI) │
│ ├─ /v1/articles /v1/sources /v1/subscriptions │
│ ├─ /v1/auth /v1/me /v1/search │
│ └─ /v1/admin/* (源/翻译/任务) │
└──────────────────┬───────────────────────────────────────┘
┌──────────────────▼───────────────────────────────────────┐
│ 业务服务层 (Service) │
│ ├─ article_service source_service │
│ ├─ translation_service (配额/降级/缓存) │
│ ├─ search_service subscription_service │
│ └─ enrichment_service (分类/点评/实体)预留 │
└──────────────────┬───────────────────────────────────────┘
┌──────────────────▼───────────────────────────────────────┐
│ 数据访问层 (Repository / ORM) │
│ └─ SQLAlchemy 2.0 (async) · Alembic 迁移 │
└──────────────────┬───────────────────────────────────────┘
┌──────────────────▼───────────────────────────────────────┐
│ 存储层 │
│ ├─ PostgreSQL 16 (结构化 + 全文检索) │
│ ├─ Redis 7 (缓存 / 队列 / 限流) │
│ └─ 本地文件系统 (HTML 快照 / 图片,可选) │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ 后台层 (Background / Off-band) │
│ ├─ Scheduler (APScheduler) → 触发各源采集 │
│ ├─ Worker (asyncio task pool) → 解析/翻译/入库存 │
│ └─ Watchdog (健康检查 + 失败重试 + 告警) │
└──────────────────────────────────────────────────────────┘
```
### 2.2 部署视图(单机,30G 强约束)
```
┌──────── 香港 VPS (30G SSD) ────────┐
│ Docker Compose │
│ ├─ caddy (反代 + HTTPS) │
│ ├─ api (FastAPI) │
│ ├─ worker (后台 worker) │
│ ├─ scheduler (APScheduler) │
│ ├─ postgres (主库) │
│ ├─ redis (缓存) │
│ └─ meilisearch (可选,全文搜索) │
└────────────────────────────────────┘
```
- 数据卷预估(30G 总盘):
- 系统 + Docker images ≈ 5G
- PostgreSQL 30 天热数据 ≈ 3G(压缩 + 文本截断 8KB/篇上限)
- Redis 内存上限 256MB
- HTML 快照(可选,默认关闭)≈ 1G/30天
- 留 10G 以上 buffer → ✅ 余量充足
### 2.3 数据流(单篇文章生命周期)
```
[源站] → RSS/HTTP → 抓取器(原文 HTML)
解析(title/body/url/published_at/source)
去重(url hash + title simhash)
入 articles 表(原文)
翻译任务入队 → Tencent API → 译文回写
(可选)分类 / 实体 / 摘要任务
API 可被消费
```
---
## 3. 技术选型(明确推荐 + 备选)
| 层 | 推荐 | 备选 | 选它的理由 |
| --- | --- | --- | --- |
| 后端框架 | **Python 3.12 + FastAPI** | Node.js Nest / Go Fiber | 爬虫/解析/ML 库生态最好;中文分词/jieba 顺手 |
| ORM | SQLAlchemy 2.0(async) + Alembic | Tortoise ORM | 生态最熟,迁移稳 |
| 数据库 | **PostgreSQL 16** | SQLite | 全文检索 / JSONB / FTS 都好;SQLite 不支持并发写 |
| 缓存/队列 | **Redis 7** | dramatiq 单机 | 后续好扩;BullMQ 思路熟悉 |
| 任务调度 | APScheduler(进程内) | Celery beat | 30G 单机不需要重型 Celery |
| RSS 解析 | feedparser | — | 工业标准 |
| HTML 抽取 | **trafilatura** | newspaper3k / readability | 多语种 + 准确率高 + 快 |
| 动态渲染 | playwright(按需) | — | 仅对 JS 站点启用 |
| 翻译 SDK | tencentcloud-sdk-python | — | 官方 |
| 本地翻译 | **NLLB-200-distilled-1.3B**(INT8) | LibreTranslate | 离线 fallback |
| 网页前端 | **Vue 3 + Vite + Pinia + Naive UI** | Nuxt / SvelteKit | 学习成本低,产物轻 |
| Android | **Kotlin + Compose + Hilt + Retrofit + Room** | — | 你定 Kotlin |
| 反代 | Caddy | Nginx | 自动 HTTPS,配置短 |
| 监控 | Uptime Kuma(可选容器) | — | 1 个 docker,UI 美 |
> 30G 硬盘上不跑 ML 模型服务(太大)。本地翻译做成"按需调用小模型"或"调用本机 HTTP 接口",模型文件按需下载或不放服务器。
---
## 4. 采集层详细设计
### 4.1 源的分类(决定采集器)
| 类别 | 例子 | 采集器 |
| --- | --- | --- |
| RSS 完整 | Reuters / AP / BBC / Al Jazeera / NHK / DW / France24 | `feedparser` |
| RSS 部分 | NYT / Guardian(部分 RSS) | `feedparser` + 抓详情 |
| HTML 列表页 | Ground News / Bing News | `trafilatura` + 列表抽取 |
| Twitter/X | 暂不接(反爬代价高) | — |
| Telegram Channel | 用户后续可加 | Telethon 客户端 |
### 4.2 源配置(数据库表)
```sql
CREATE TABLE sources (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL, -- 'Reuters World'
slug TEXT UNIQUE NOT NULL, -- 'reuters-world'
kind TEXT NOT NULL, -- 'rss' | 'html_list' | 'tg_channel'
url TEXT NOT NULL, -- RSS URL 或 列表 URL
detail_selector JSONB, -- 详情页抽取规则(非RSS)
fetch_interval_min INT NOT NULL DEFAULT 60,
fetch_cron TEXT, -- 可选,覆盖 interval,例 '15 2 * * *'
translate_to TEXT NOT NULL DEFAULT 'zh', -- 目标语言
enabled BOOLEAN NOT NULL DEFAULT TRUE,
region TEXT, -- 'global' | 'eu' | 'cn' | ...
language_src TEXT, -- 源语种 'en' | 'auto'
priority INT DEFAULT 50, -- 1-100,影响翻译优先级
headers_json JSONB, -- 自定义 UA/Cookie
last_fetched_at TIMESTAMPTZ,
last_status TEXT, -- 'ok' | 'fail:timeout' ...
created_at TIMESTAMPTZ DEFAULT now()
);
```
> **手工配合实现 RSS 源预定**:暴露 `/v1/admin/sources` 的 CRUD + 网页表单,你手工一条条加。MVP 不做 OPML 导入,但保留口子。
### 4.3 调度策略
- **错峰**:不一次性 wake-up 全部源。按源的 `priority` + `region` 哈希,凌晨分散到不同分钟。
- **分层时间窗**:
- `priority ≥ 80`:每 30 分钟
- `priority 50~79`:每 2 小时
- `priority < 50`:每 6 小时 / 每日
- **退避**:某源连续 3 次失败,自动把 `fetch_interval_min` × 2,封顶 720min;成功一次后恢复。
- **统一超时**:单源 fetch ≤ 20s,parse ≤ 10s,失败即记日志,不入主流程。
### 4.4 去重
三层去重,严格度递减:
1. **URL 规范化 hash**(主键之 `url_hash` UNIQUE):去除 `utm_*`、hash fragment、尾斜杠。
2. **title simhash**:相同事件不同 URL 合并(MVP 标 `duplicate_of` 字段)。
3. **嵌入向量余弦**(后续):同主题聚类展示。
### 4.5 抓取器容错
- 限速:全局 QPS ≤ 4(礼貌),单源失败重试 3 次,指数退避。
- 代理:暂不用,先直连。香港出去对欧美/日本都通。
- 反爬:合规 UA + 极简 Cookie;若某源 `403/429` 高发,接 `playwright` 兜底。
- 法律:每篇保留来源链接 + 发布时间,不做全文二改,只翻译展示。
---
## 5. 翻译层详细设计
### 5.1 翻译策略(总分总)
| 字段 | 是否翻译 | 说明 |
| --- | --- | --- |
| title | ✅ 强制 | 优先级最高 |
| summary(若有) | ✅ 强制 | 摘要抽取后续接 |
| body(HTML→纯文本) | ✅ 强制 | 截断 8000 字/篇,超过分段 |
| 段落内嵌 HTML 标签 | ❌ 保留 | 翻译完按位置回插(用 `data-idx` 占位) |
**分块规则**:
- 按段落切分(双换行)
- 单段 > 1500 字符 → 强制按句号再切
- 单次 API 请求 body ≤ 5000 字符(腾讯 TMT 限制)
- 译文回写时按 `data-idx` 还原 DOM 结构
### 5.2 字符计量(关键:别超 500 万/月)
```python
# 月度计数器(Redis)
translation:month:202606 = 124533 # 当月已用
# 公式
total_chars = sum(len(seg.encode('utf-8')) // 2 + 1 for seg in segments)
# 用 Unicode 码点近似,腾讯 TMT 实际按"字符数"计
# 流程
pre_check(待翻译字符数) if (已用+本次) < 500 走腾讯
else 走本地 NLLB
```
- **保留 5% 缓冲**:到达 475 万字符,自动切本地。
- **每月 1 日 00:00 HKT** 重置(用 cron + Redis SET)。
### 5.3 翻译缓存(白嫖配额)
- 缓存键:`sha1(source_id + url_hash + lang_pair + text)`
- 命中直接返回,不计字符。
- 命中率经验值:同源同事件重复抓 30~60%,月省 30%+ 字符。
### 5.4 失败降级
| 失败类型 | 处置 |
| --- | --- |
| 腾讯 TMT 429/5xx | 重试 2 次,仍失败 → 写 `translation_status='pending'`,后台重排 |
| 配额耗尽 | 切本地 NLLB(离线 INT8 模型) |
| 模型文件缺失 | 直接用原文 + 文末 `[本条未翻译]` 标记 |
| 段落超长截断 | 截断后用 `[...]` 占位,用户可点"展开"看原文 |
### 5.5 译文表结构(节选)
```sql
CREATE TABLE articles (
id BIGSERIAL PRIMARY KEY,
source_id BIGINT REFERENCES sources(id) ON DELETE CASCADE,
url TEXT NOT NULL,
url_hash CHAR(40) NOT NULL UNIQUE,
title TEXT NOT NULL,
title_zh TEXT,
body_html TEXT, -- 抽取后保留结构
body_text TEXT NOT NULL, -- 纯文本
body_zh_html TEXT,
body_zh_text TEXT,
lang_src TEXT, -- 'en' | 'ja' | ...
published_at TIMESTAMPTZ,
fetched_at TIMESTAMPTZ DEFAULT now(),
translated_at TIMESTAMPTZ,
translation_status TEXT DEFAULT 'pending', -- pending|ok|partial|failed|n/a
translation_engine TEXT, -- 'tencent' | 'nllb' | 'cache'
translation_chars INT DEFAULT 0,
category TEXT, -- 预留
commentary TEXT, -- 预留
entities JSONB, -- 预留
sentiment REAL, -- 预留 -1..1
duplicate_of BIGINT REFERENCES articles(id),
is_starred BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX articles_published_at_idx ON articles (published_at DESC);
CREATE INDEX articles_source_id_idx ON articles (source_id);
CREATE INDEX articles_fts_idx ON articles
USING GIN (to_tsvector('simple', coalesce(title,'') || ' ' || coalesce(body_text,'')));
```
---
## 6. 数据层
### 6.1 PostgreSQL 必备扩展
- `pg_trgm`:title 相似度去重
- `btree_gin` / `pg_stat_statements`:性能/诊断
- `uuid-ossp`:不必须,主键用 BIGSERIAL
### 6.2 核心表(全清单)
```
sources 采集源配置
articles 文章主表(原文 + 译文)
article_media 文章图片/附件(默认只存 url)
users 用户
subscriptions 关键词订阅
bookmarks 收藏
read_history 阅读历史(可后续分析)
api_tokens API token(给 Android 用)
audit_logs 关键操作审计
```
### 6.3 备份
- `pg_dump` 每日凌晨 4 点 → 压缩到本地 `/var/backups/pg/`
- 保留 7 天滚动
- 强烈建议再 push 到一个外部对象存储(腾讯云 COS/阿里云 OSS)做异地灾备
- 30G 盘放不下 7 天全量 dump → 增量 + 每周一全量
### 6.4 冷热分层(防 30G 撑爆)
- 热数据:`published_at > now() - 30 day` 全字段
- 冷数据:`published_at <= now() - 30 day` 只保留 `title`/`title_zh`/`url`/`body_zh_text`(丢弃 `body_html` 原文)
- 90 天以上:进入"归档表" `articles_archive`,主表查询更轻
---
## 7. API 设计(RESTful + JSON)
### 7.1 设计原则
- 资源用复数名词:`/v1/articles`
- 时间分页用 `?since=<rfc3339>&until=...&limit=...&cursor=...`
- 错误用 RFC 7807 `application/problem+json`
- 列表返回精简字段,详情返回全字段
- 所有时间 ISO8601 + UTC(前端自行渲染本地)
### 7.2 鉴权
- 登录:`POST /v1/auth/login` → 返回 access(15min) + refresh(7d)
- APP:用长期 API Token(`api_tokens` 表),可撤销
- Admin 接口:`/v1/admin/*` 强制 `role=owner`
### 7.3 核心端点(MVP)
| Method | Path | 说明 |
| --- | --- | --- |
| `GET` | `/v1/articles` | 列表,默认过去 24h。支持 `source` / `lang` / `q` / `cursor` |
| `GET` | `/v1/articles/{id}` | 详情(原文 + 译文 + 媒体 + 实体) |
| `GET` | `/v1/sources` | 源列表(已登录用户) |
| `GET` | `/v1/me` | 当前用户信息 + 翻译配额 |
| `POST` | `/v1/bookmarks` / `DELETE /v1/bookmarks/{article_id}` | 收藏 |
| `POST` | `/v1/subscriptions` | 创建关键词订阅(正则/简单词) |
| `GET` | `/v1/search?q=...` | 全文检索 |
| `GET` | `/v1/stats/usage` | 翻译字符用量,管理端可见 |
| `POST` | `/v1/admin/sources` | 新增/更新源 |
| `POST` | `/v1/admin/refresh/{source_id}` | 手动触发某源抓取 |
| `POST` | `/v1/admin/translation/rerun/{article_id}` | 重译 |
### 7.4 示例
```http
GET /v1/articles?since=2026-06-07T00:00:00Z&limit=50&source=reuters,bbc
Authorization: Bearer <token>
200 OK
{
"items": [
{
"id": 1234,
"source": {"id": 1, "name": "Reuters", "region": "global"},
"title": "Fed signals ...",
"title_zh": " ...",
"published_at": "2026-06-07T08:32:00Z",
"lang_src": "en",
"translation_status": "ok",
"summary_zh": "...",
"category": "finance",
"has_commentary": false
}
],
"next_cursor": "eyJ..."
}
```
---
## 8. 表现层
### 8.1 网页(Web)
- **首屏**:过去 24h 卡片瀑布,按时间倒序。可切"按源" / "按地区"。
- **详情页**:左原文 / 右译文并列,可关一侧;底部"分类 / 点评 / 实体 / 相关"预留位。
- **暗模式**:跟随系统。
- **PWA**:可"添加到主屏幕",离线缓存最近 50 篇。
- **技术**:Vue 3 + Vite + Pinia + Vue Router + Naive UI;纯静态构建,首次加载 < 200KB gzip。
### 8.2 Android App
- **架构**:Clean Architecture(Compose UI → ViewModel → UseCase → Repository → Retrofit/Room)
- **关键页面**:
1. Feed(24h 列表,下拉刷新、无限滚动)
2. 详情(原文/译文 Tab 切换,字号调节,长按收藏,生词本高亮)
3. 源管理(只读 + 启停)
4. 订阅/收藏
5. 搜索
6. 设置(主题、语种、配额、API 地址)
- **关键库**:Hilt、Retrofit + OkHttp、Kotlinx Serialization、Room(本地缓存)、WorkManager(周期同步 + 推送通道预留)、DataStore(偏好)
- **目标**:APK < 10MB,minSdk 26。
---
## 9. 机器学习/智能功能(预留接口,MVP 不实现)
| 功能 | 字段 | 触发 | 模型 |
| --- | --- | --- | --- |
| 自动分类 | `category` | 入库后 | zero-shot:MDeBERTa / bge 嵌入 + 简单聚类 |
| 一句话点评 | `commentary` | 入库后 | 小尺寸 instruct LLM,本地或 API |
| 实体识别 | `entities`(人/地/机) | 入库后 | GLiNER / spaCy |
| 情感 | `sentiment` | 入库后 | twitter-roberta |
| 摘要 | `summary_zh` | 入库后 | 抽取式(首段+关键句)优先 |
| 主题聚类 | `topic_id` | 每日跑 | DBSCAN / HDBSCAN |
| 跨源立场 | `bias_left/center/right` | 后续 | 对接 Ground News 数据 |
所有这些做成 **enrichment pipeline**,在 worker 里顺序挂 hook,MVP 全部 `null`,字段都在。
---
## 10. 部署与运维
### 10.1 目录结构
```
/srv/news/
├─ docker-compose.yml
├─ .env # 密钥
├─ caddy/Caddyfile
├─ api/
├─ worker/
├─ scheduler/
├─ migrations/ # alembic
└─ data/
├─ postgres/
└─ backups/
```
### 10.2 .env(示例,不要提交)
```
POSTGRES_PASSWORD=***
REDIS_PASSWORD=***
TENCENTCLOUD_SECRET_ID=***
TENCENTCLOUD_SECRET_KEY=***
TENCENTCLOUD_REGION=ap-hongkong
TENCENT_TMT_QUOTA_MONTH=5_000_000
JWT_SECRET=***
DOMAIN=news.example.com # 或裸 IP
```
### 10.3 启动
```bash
docker compose up -d
docker compose exec api alembic upgrade head
docker compose exec api python -m app.scripts.seed_sources
```
### 10.4 监控
- Uptime Kuma:Docker 一行,UI 看 API/Postgres/Redis 状态
- API 自带 `/healthz``/metrics`(Prometheus 格式,可选)
- 翻译配额告警:每 6h 检查,> 80% 推送到 Telegram bot
### 10.5 安全
- Caddy 自动 HTTPS(域名)或自签(裸 IP)
- Fail2ban 守护 SSH
- 数据库/Redis 不暴露 5432/6379 端口
- API 限流:每用户 60 req/min(IP + user 双维度)
- 密码 bcrypt cost=12
---
## 11. 开发路线图(我自己拍的时间,别太较真)
### Phase 0 · 立项(1 天)
- 决策:技术栈 + 服务器初始化
- 建仓 + CI 草稿
### Phase 1 · MVP(2~3 周)
- ✅ 采集器(RSS 5 个源,跑通)
- ✅ 入库 + 翻译(腾讯云,带缓存)
- ✅ 网页:列表 + 详情
- ✅ 鉴权(单用户,自用)
- ✅ 凌晨调度跑通
### Phase 2 · 扩源 + 体验(2 周)
- 源扩到 20+,加 HTML 列表
- 网页:筛选、搜索、PWA
- 配额监控 + 告警
- 备份脚本
### Phase 3 · Android(2~3 周)
- APP 端到端
- 推送通道(可选 FCM / WebPush → Telegram)
- 离线阅读
### Phase 4 · 智能增强(2 周+)
- 分类/摘要/实体
- 主题聚类 + 周报邮件
- 跨源立场
---
## 12. 补充功能(你可能没想到的,先列给你筛)
### 12.1 内容维度
- **全文搜索** + 高亮(网页 + APP 双端)
- **关键词订阅**:命中即通知/邮件
- **收藏 + 标签**:自建标签,可视化分类
- **阅读历史 + 统计**:今日/本周阅读时长
- **导出**:单篇 Markdown / 批量 CSV / 邮件简报
- **每日精选邮件**(凌晨 7 点,前 24h 摘要)
- **实体时间线**:同一人物/机构多源跟踪
- **反信息茧房视图**:同一事件并排多源报道
- **媒体偏见可视化**(后续接 Ground News 数据)
### 12.2 系统维度
- **RSS 源管理 UI**(代替手工 SQL)
- **翻译配额仪表盘**(用量趋势/剩余天数)
- **源健康看板**(抓取成功率、平均延迟)
- **手动重抓 / 重译**
- **内容删除/合规** 工具(被 DMCA 时一键下架)
- **多用户**:家人/朋友的子账号 + 独立收藏
- **iOS PWA**:用网页 PWA 顶一下,先不写原生
- **Telegram 推送机器人**:最简通知渠道
- **2FA / Passkey**:管理员登录加一道锁
- **API Token**:给 Android 用,可独立撤销
- **审计日志**:谁看了什么(隐私 vs 共享的可调)
- **全文快照开关**:合规要求下可一键保留 HTML 留证
### 12.3 体验维度
- **暗模式**(网页 + APP)
- **字号/行距** 调节
- **TTS 朗读**(浏览器 `SpeechSynthesis` 一行调用)
- **生词本 / 翻译对照高亮**
- **滑动操作**(APP 端快速收藏/已读)
- **桌面小组件**(APP)
- **侧边栏:今日摘要 + 配额 + 源状态**
- **快捷键**(网页 j/k 上下条,o 打开,s 收藏)
### 12.4 还没想清楚(留个尾巴)
- **法律边界**:翻译展示是否构成"公开展示"?如果是私人圈(2~10 人)风险低;但要避免做"开放注册"
- **内容版权**:只展示摘要 + 链接,正文翻译控制在合理比例(MVP 设 8KB 上限,实际只占原文 30~60%)
- **是否做付费墙破解**:❌ 不做,放弃相应源
---
## 13. 风险与对策
| 风险 | 概率 | 影响 | 对策 |
| --- | --- | --- | --- |
| 源站改版,抓取失效 | 高 | 中 | 解析器独立可热更新;失败率高自动暂停源 |
| 腾讯翻译额度爆 | 中 | 中 | 本地 NLLB 兜底;每日用量监控 |
| 香港 VPS 被墙/限速 | 中 | 高 | 多源冗余;备选新加坡/日本节点 |
| 30G 硬盘撑爆 | 低 | 高 | 冷热分层 + 外部备份 |
| 源站发律师函 | 低 | 高 | 摘要化展示 + 链接跳转原文 |
| 多人共用导致配额紧张 | 中 | 低 | 默认每日 10 万字符上限,可调 |
---
## 14. 第一次跑起来你要做的事
1. 服务器初始化(Ubuntu 24 + Docker + Caddy)
2. 注册腾讯云账号,开通文本翻译 TMT,拿 Secret
3. 选 5 个最常用的源(我建议:Reuters、BBC World、Al Jazeera、NHK World、DW),先把 RSS URL 备齐
4. 我这边起仓写代码,3 周后跑 MVP
5. 跑稳后加 Android + 智能功能
---
**下一步**:你看完这个方案,挑几个点:
- ① 哪些功能要砍/加?
- ② 技术栈有没有要换的(比如你就是想用 NestJS 不用 FastAPI)?
- ③ MVP 的 5 个源定哪几个?
- ④ 想不想让我现在就开始 Phase 1 的代码?
回我一句就行。