Files
diary-news/docs/architecture.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

78 lines
2.3 KiB
Markdown
Raw 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 的实现版本。
## 模块边界
| 模块 | 路径 | 职责 |
| --- | --- | --- |
| 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) 看全貌。