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
This commit is contained in:
65
docs/acceptance.md
Normal file
65
docs/acceptance.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# MVP 验收清单(Phase 1)
|
||||
|
||||
部署完成后,逐项验证;任何一项失败先看 `DEPLOY.md` 的 FAQ + `docker compose logs`。
|
||||
|
||||
## 0. 服务健康
|
||||
|
||||
- [ ] `docker compose ps` 所有服务 `running`
|
||||
- [ ] `curl http://YOUR_IP/api/v1/healthz` → `{"status":"ok"}`
|
||||
- [ ] `curl http://YOUR_IP/` → 返回 HTML(SPA 入口)
|
||||
|
||||
## 1. 账号与登录
|
||||
|
||||
- [ ] `docker compose exec api python -m app.scripts.create_user --username owner --password XXXX`
|
||||
- [ ] 浏览器打开首页 → 登录页 → 用 owner 登录成功
|
||||
- [ ] 侧边栏显示 "owner (owner)"
|
||||
- [ ] 顶栏翻译配额显示 "翻译: 0 / 5,000,000 (0.0%)"
|
||||
|
||||
## 2. 源管理
|
||||
|
||||
- [ ] `docker compose exec api python -m app.scripts.seed_sources`
|
||||
- [ ] 进入 "源管理(Admin)" 页,看到 5 条源(Reuters/BBC/Al Jazeera/NHK/DW)
|
||||
- [ ] "源健康" 页 5 个源都在
|
||||
- [ ] 某个源点 "立即抓取" → message 提示 "已加入抓取队列"
|
||||
- [ ] 等 1~2 分钟,看 worker 日志:`docker compose logs -f worker | grep -E "fetch|articles"`
|
||||
|
||||
## 3. 文章采集与展示
|
||||
|
||||
- [ ] "24h 列表" 页有文章(数量与抓取量相关,首次可能 10~50 条)
|
||||
- [ ] 卡片显示:源标签 / 语种 / 发布时间 / 英文标题 / 中文标题
|
||||
- [ ] 顶栏配额数字 > 0(说明翻译已消耗字符)
|
||||
- [ ] 点开文章详情:
|
||||
- [ ] 原文 + 译文双卡片
|
||||
- [ ] 翻译状态为 "ok"
|
||||
- [ ] 翻译引擎为 "tencent"
|
||||
- [ ] 点 "原文链接 ↗" 跳到源站
|
||||
- [ ] 部分文章可能还是 "pending" / "partial",等下个 worker 周期
|
||||
|
||||
## 4. 收藏
|
||||
|
||||
- [ ] 文章详情点 "☆ 收藏" → 变 "★ 已收藏"
|
||||
- [ ] 侧边栏 "收藏" 看到刚才那篇
|
||||
- [ ] 列表页对应卡片有 star 标记(后续可加视觉)
|
||||
|
||||
## 5. 失败降级验证(可选)
|
||||
|
||||
- [ ] 把腾讯云 secret 配错 → 重启 worker → 下次抓取会触发翻译失败
|
||||
- [ ] 文章状态变为 "failed",详情页有黄色提示
|
||||
- [ ] 改为正确 secret → 点 "重译" → 成功
|
||||
|
||||
## 6. 调度器验证
|
||||
|
||||
- [ ] `docker compose logs -f worker` → 应看到 "scheduler started with N jobs"
|
||||
- [ ] 看到 "scheduled <slug> every <interval>"
|
||||
- [ ] 等过 fetch_interval_min 分钟后,日志有新的 "source X: N new articles"
|
||||
|
||||
## 7. 配额监控(可选)
|
||||
|
||||
- [ ] `curl -H "Authorization: Bearer $TOKEN" http://YOUR_IP/api/v1/me/usage`
|
||||
- [ ] 看到 used_chars > 0
|
||||
|
||||
## 8. 验收完成
|
||||
|
||||
- [ ] 全 7 项通过 → Phase 1 验收 ✅
|
||||
- [ ] 任意失败 → 看 `docker compose logs <service>` + `DEPLOY.md` FAQ
|
||||
- [ ] 性能 / 体验改进 → 进 Phase 2
|
||||
77
docs/architecture.md
Normal file
77
docs/architecture.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 架构设计
|
||||
|
||||
> 对应方案 v0.1 的实现版本。
|
||||
|
||||
## 模块边界
|
||||
|
||||
| 模块 | 路径 | 职责 |
|
||||
| --- | --- | --- |
|
||||
| API | `backend/app/api/` | HTTP 路由,处理鉴权 / 入参 / 出参 |
|
||||
| 业务 | `backend/app/services/` | 抓取、翻译、领域逻辑 |
|
||||
| Worker | `backend/app/workers/` | 后台调度、pipeline |
|
||||
| 数据 | `backend/app/models/` | SQLAlchemy ORM |
|
||||
| 迁移 | `backend/alembic/` | 数据库 schema 版本 |
|
||||
| 前端 | `frontend/src/` | Vue 3 + Naive UI |
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
Source (DB)
|
||||
│
|
||||
▼
|
||||
Scheduler (cron / interval)
|
||||
│
|
||||
▼
|
||||
RSSFetcher.fetch() ── HTTP GET ──► upstream RSS
|
||||
│
|
||||
▼
|
||||
FetchedItem list
|
||||
│
|
||||
▼ (url_hash UNIQUE 去重)
|
||||
Article INSERT
|
||||
│
|
||||
▼ (translation_status='pending')
|
||||
TranslationService.translate()
|
||||
├─ Redis cache hit → return
|
||||
├─ quota check
|
||||
├─ Tencent TMT (主) ──► 30 天 Redis 月度计数
|
||||
└─ Local NLLB (降级,需启用)
|
||||
│
|
||||
▼
|
||||
Article UPDATE (title_zh / body_zh_* / status)
|
||||
```
|
||||
|
||||
## 关键设计决策
|
||||
|
||||
- **PostgreSQL**:UNIQUE 约束 + `ON CONFLICT DO NOTHING` 做去重,O(1) 写
|
||||
- **Redis 三用**:翻译缓存(30 天 TTL)+ 月度配额(INCRBY)+ 后续限流
|
||||
- **AScheduler 重构 jobs**:每天 00:30 从 DB 重新读,运行时新增源自动生效
|
||||
- **翻译分块**:按段落切,单段 > 1500 字符按句号再切,单请求 ≤ 4500 字符(腾讯 TMT 上限)
|
||||
- **失败退避**:某源连续失败 3 次,fetch_interval × 2(封顶 720 分钟),成功一次恢复
|
||||
- **API Token 双轨**:网页用短期 JWT(15min)+ refresh(14d);Android 用长期 API Token(可独立撤销)
|
||||
|
||||
## 字段保留
|
||||
|
||||
`articles` 表里这些字段已建,MVP 全部 null,后续 enrichment 阶段直接写值不动表:
|
||||
- `category` / `commentary` / `entities` / `sentiment` / `topic_id` / `bias`
|
||||
|
||||
## 安全
|
||||
|
||||
- 密码 bcrypt(cost=12)
|
||||
- JWT 走 HTTPS-only cookie(网页) / Bearer header(APP)
|
||||
- 数据库/Redis 不暴露到宿主机
|
||||
- Caddy 做 TLS 终止
|
||||
- API 限流(MVP 暂未实现,后续加)
|
||||
|
||||
## 不在 MVP
|
||||
|
||||
- ❌ 全文搜索(可用 PG `to_tsvector`,MVP 先简单 ILIKE)
|
||||
- ❌ PWA 离线缓存
|
||||
- ❌ Android 客户端
|
||||
- ❌ 自动分类/点评/实体识别
|
||||
- ❌ 主题聚类
|
||||
- ❌ 跨源立场
|
||||
- ❌ Telegram 推送
|
||||
- ❌ i18n(只 zh)
|
||||
|
||||
见 [`DEPLOY.md`](../DEPLOY.md) 跑起来,见 [`../README.md`](../README.md) 看全貌。
|
||||
Reference in New Issue
Block a user