9.5 KiB
9.5 KiB
后端运行逻辑
后端是 Node.js Express + SQLite + WebSocket,与 Python 爬虫共用同一数据库文件,负责提供「态势数据」API、实时推送和简单统计。
一、启动方式
npm run api # 启动 server/index.js,默认端口 3001
- 端口:
process.env.API_PORT || 3001 - 数据库:
process.env.DB_PATH或server/data.db(与爬虫共用)
二、整体架构
┌─────────────────────────────────────────┐
│ server/index.js │
│ (HTTP Server + WebSocket Server) │
└─────────────────────────────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ /api/* │ │ /ws │ │ 静态 dist │
│ routes.js │ │ WebSocket │ │ (生产) │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
│ 读/写 │ 广播 situation + stats
▼ │
┌─────────────┐ │
│ db.js │◄─────────────────────┘
│ (SQLite) │ getSituation() / getStats()
└──────┬──────┘
│
│ 同文件 data.db
▼
┌─────────────┐
│ Python 爬虫 │ 抓取 → 去重 → AI 清洗 → 映射到库字段 → 写表 → POST /api/crawler/notify
│ situation_ │ (main.py 或 gdelt 服务;写 situation_update / news_content / combat_losses 等)
│ update 等 │
└─────────────┘
三、核心模块
| 文件 | 作用 |
|---|---|
| index.js | 创建 HTTP + WebSocket 服务,挂载路由、静态资源、定时广播、爬虫通知回调 |
| routes.js | 所有 /api/* 接口:situation、db/dashboard、visit、feedback、share、stats、events、news 等 |
| situationData.js | getSituation():从多张表聚合为前端所需的「态势」JSON(军力、基地、战损、事件脉络、GDELT 等) |
| db.js | SQLite 连接、建表、迁移(better-sqlite3,WAL 模式) |
| stats.js | getStats():在看人数、累计访问、留言数、分享数 |
| openapi.js | Swagger/OpenAPI 文档定义 |
| seed.js | 初始化/重置种子数据(可单独运行 npm run api:seed) |
四、数据流(读)
-
前端要「整页态势」
- 请求
GET /api/situation→routes.js调用getSituation() situationData.js从 db 读:force_summary、power_index、force_asset、key_location、combat_losses、wall_street_trend、retaliation_*、situation_update(最近 50 条)、gdelt_events、conflict_stats等- 组装成
{ lastUpdated, usForces, iranForces, recentUpdates, conflictEvents, conflictStats, civilianCasualtiesTotal }返回。
- 请求
-
前端要「事件列表」
GET /api/events返回conflictEvents+conflict_stats+updated_at(同样来自 getSituation 的数据)。
-
前端要「原始表数据」
GET /api/db/dashboard返回多张表的SELECT *结果(含situation_update),供/db调试页使用。
-
WebSocket
- 连接
ws://host/ws时立即收到一条{ type: 'situation', data: getSituation(), stats: getStats() }。 - 之后每 3 秒服务端主动广播同结构数据,前端可据此做实时刷新。
- 连接
五、数据流(写)
5.1 爬虫侧写库链路(推荐理解顺序)
爬虫写入前端库的完整链路如下,不是「抓完直接写表」,而是经过去重、AI 清洗、字段映射后再落库:
-
爬虫抓取实时数据
- RSS 等源抓取(
scrapers/rss_scraper.fetch_all),得到原始条目列表。
- RSS 等源抓取(
-
数据去重
- 抓取阶段:RSS 内按 (title, url) 去重。
- 落库前:按
content_hash(title, summary, url)在news_content表中去重,仅未出现过的条目进入后续流程(news_storage.save_and_dedup)。
-
去重后按批次推送给 AI 清洗
- 对通过去重的每条/每批数据:
- 展示用清洗:标题/摘要翻译、
clean_news_for_panel提炼为符合面板的纯文本与长度(如 summary ≤120 字),ensure_category/ensure_severity规范为前端枚举(cleaner_ai)。 - 结构化提取(可选):
extractor_ai/extractor_dashscope/extractor_rules从新闻文本中抽取战损、基地状态等,输出符合panel_schema的结构。
- 展示用清洗:标题/摘要翻译、
- 得到「有效数据」:既有人读的 summary/category/severity,也有可落库的 combat_losses_delta、key_location 等。
- 对通过去重的每条/每批数据:
-
有效数据映射回前端数据库字段
- 事件脉络:清洗后的条目写入
situation_update(db_writer.write_updates)。 - 资讯存档:去重后的新数据写入
news_content(已在步骤 2 完成)。 - 结构化数据:AI 提取结果通过
db_merge.merge映射到前端表结构,更新combat_losses、key_location、retaliation_*、wall_street_trend等(与situationData.getSituation所用字段一致)。
- 事件脉络:清洗后的条目写入
-
更新数据库表并通知后端
- 上述表更新完成后,爬虫请求 POST /api/crawler/notify。
- 后端(index.js)更新
situation.updated_at并调用broadcastSituation(),前端通过 WebSocket 拿到最新态势。
实现上,gdelt 服务(realtime_conflict_service)里:先对抓取结果做翻译与清洗,再 save_and_dedup 去重落库 news_content,用去重后的新项写 situation_update,再按批次对这批新项做 AI 提取并 db_merge.merge 写战损/基地等表。
5.2 用户行为写入
- POST /api/visit:记 IP 到
visits,visitor_count.total+1,并触发一次广播。 - POST /api/feedback:插入
feedback。 - POST /api/share:
share_count.total+1。
这些写操作在 routes.js 中通过 db.prepare().run() 完成。
六、API 一览
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/health | 健康检查 |
| GET | /api/situation | 完整态势(供主面板) |
| GET | /api/events | 冲突事件 + 统计 |
| GET | /api/db/dashboard | 各表原始数据(供 /db 页) |
| GET | /api/news | 资讯列表(news_content 表) |
| GET | /api/stats | 在看/累计/留言/分享数 |
| POST | /api/visit | 记录访问并返回 stats |
| POST | /api/feedback | 提交留言 |
| POST | /api/share | 分享计数 +1 |
| POST | /api/crawler/notify | 爬虫通知:更新 updated_at 并广播(内部用) |
- Swagger:
http://localhost:3001/api-docs
七、WebSocket 行为
- 路径:
/ws(与 HTTP 同端口)。 - 连接时:服务端发送一条
{ type: 'situation', data, stats }。 - 定时广播:
setInterval(broadcastSituation, 3000)每 3 秒向所有已连接客户端推送最新getSituation()+getStats()。 - 爬虫通知:POST
/api/crawler/notify会立即执行一次broadcastSituation(),不必等 3 秒。
八、与爬虫的协作
- 共享 DB:后端与爬虫都使用同一
DB_PATH(默认server/data.db)。 - 爬虫写库链路:爬虫抓取 → 去重 → AI 清洗出有效数据 → 映射到前端库字段 → 更新
situation_update、news_content、combat_losses、key_location、gdelt_events等表 → 调用 POST/api/crawler/notify通知后端。 - 后端角色:只读这些表(
getSituation()等)并推送;不参与抓取、去重或 AI 清洗,不调度爬虫。
整体上,后端是「读库 + 聚合 + 推送」的服务;写库来自爬虫(经过去重与 AI 清洗、字段映射后)以及用户行为(访问/留言/分享)。
九、本地验证链路
- 启动后端:
npm run api(默认 3001)。 - 检查读库:
curl -s http://localhost:3001/api/situation应返回含lastUpdated、recentUpdates的 JSON。 - 检查写库与通知:爬虫跑完流水线后会 POST
/api/crawler/notify,后端会更新situation.updated_at并广播;可再请求/api/situation看lastUpdated是否更新。 - 查原始表:浏览器打开
http://localhost:3001/api/db/dashboard或前端/db页,查看situation_update、news_content等表。
爬虫侧完整验证步骤见 crawler/README.md 的「本地验证链路」;项目根目录可执行 ./scripts/verify-pipeline.sh 做一键检查。