614 lines
24 KiB
Markdown
614 lines
24 KiB
Markdown
|
|
# 私人新闻汇总系统 · 方案设计 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 的代码?
|
|||
|
|
|
|||
|
|
回我一句就行。
|