Files
usa/server/README.md
2026-03-03 14:49:02 +08:00

9.5 KiB
Raw Blame History

后端运行逻辑

后端是 Node.js Express + SQLite + WebSocket,与 Python 爬虫共用同一数据库文件负责提供「态势数据」API、实时推送和简单统计。


一、启动方式

npm run api   # 启动 server/index.js默认端口 3001
  • 端口:process.env.API_PORT || 3001
  • 数据库:process.env.DB_PATHserver/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-sqlite3WAL 模式)
stats.js getStats():在看人数、累计访问、留言数、分享数
openapi.js Swagger/OpenAPI 文档定义
seed.js 初始化/重置种子数据(可单独运行 npm run api:seed

四、数据流(读)

  1. 前端要「整页态势」

    • 请求 GET /api/situationroutes.js 调用 getSituation()
    • situationData.js 从 db 读:force_summarypower_indexforce_assetkey_locationcombat_losseswall_street_trendretaliation_*situation_update(最近 50 条)、gdelt_eventsconflict_stats
    • 组装成 { lastUpdated, usForces, iranForces, recentUpdates, conflictEvents, conflictStats, civilianCasualtiesTotal } 返回。
  2. 前端要「事件列表」

    • GET /api/events 返回 conflictEvents + conflict_stats + updated_at(同样来自 getSituation 的数据)。
  3. 前端要「原始表数据」

    • GET /api/db/dashboard 返回多张表的 SELECT * 结果(含 situation_update),供 /db 调试页使用。
  4. WebSocket

    • 连接 ws://host/ws 时立即收到一条 { type: 'situation', data: getSituation(), stats: getStats() }
    • 之后每 3 秒服务端主动广播同结构数据,前端可据此做实时刷新。

五、数据流(写)

5.1 爬虫侧写库链路(推荐理解顺序)

爬虫写入前端库的完整链路如下,不是「抓完直接写表」而是经过去重、AI 清洗、字段映射后再落库:

  1. 爬虫抓取实时数据

    • RSS 等源抓取(scrapers/rss_scraper.fetch_all),得到原始条目列表。
  2. 数据去重

    • 抓取阶段RSS 内按 (title, url) 去重。
    • 落库前:按 content_hash(title, summary, url)news_content 表中去重,仅未出现过的条目进入后续流程(news_storage.save_and_dedup)。
  3. 去重后按批次推送给 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 等。
  4. 有效数据映射回前端数据库字段

    • 事件脉络:清洗后的条目写入 situation_updatedb_writer.write_updates)。
    • 资讯存档:去重后的新数据写入 news_content(已在步骤 2 完成)。
    • 结构化数据AI 提取结果通过 db_merge.merge 映射到前端表结构,更新 combat_losseskey_locationretaliation_*wall_street_trend 等(与 situationData.getSituation 所用字段一致)。
  5. 更新数据库表并通知后端

    • 上述表更新完成后,爬虫请求 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 到 visitsvisitor_count.total +1并触发一次广播。
  • POST /api/feedback:插入 feedback
  • POST /api/shareshare_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 并广播(内部用)
  • Swaggerhttp://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_updatenews_contentcombat_losseskey_locationgdelt_events 等表 → 调用 POST /api/crawler/notify 通知后端。
  • 后端角色:只读这些表(getSituation() 等)并推送;不参与抓取、去重或 AI 清洗,不调度爬虫。

整体上,后端是「读库 + 聚合 + 推送」的服务;写库来自爬虫(经过去重与 AI 清洗、字段映射后)以及用户行为(访问/留言/分享)。


九、本地验证链路

  1. 启动后端npm run api(默认 3001
  2. 检查读库curl -s http://localhost:3001/api/situation 应返回含 lastUpdatedrecentUpdates 的 JSON。
  3. 检查写库与通知:爬虫跑完流水线后会 POST /api/crawler/notify,后端会更新 situation.updated_at 并广播;可再请求 /api/situationlastUpdated 是否更新。
  4. 查原始表:浏览器打开 http://localhost:3001/api/db/dashboard 或前端 /db 页,查看 situation_updatenews_content 等表。

爬虫侧完整验证步骤见 crawler/README.md 的「本地验证链路」;项目根目录可执行 ./scripts/verify-pipeline.sh 做一键检查。