From b2b537dde5ee0125bff7ea6af2f2a485bbe1fe93 Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Sat, 22 Aug 2026 11:11:15 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E5=90=8C=E6=AD=A5=20v3?= =?UTF-8?q?=20=E8=BF=90=E5=8A=A8=E7=9B=91=E6=B5=8B=E6=9E=B6=E6=9E=84(Webho?= =?UTF-8?q?ok=20=E9=A9=B1=E5=8A=A8/=E5=8E=BB=E8=BD=AE=E8=AF=A2)=20-=20READ?= =?UTF-8?q?ME=20=E8=A1=A5=E5=85=85=202.2.1=20=E9=93=BE=E8=B7=AF/API=20?= =?UTF-8?q?=E8=A1=A8/=E6=A8=A1=E5=9D=97=E8=A1=A8,PROGRESS=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=BF=90=E8=A1=8C=E7=8A=B6=E6=80=81=E4=B8=8E=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E8=AE=B0=E5=BD=95=2051-53?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PROGRESS.md | 8 ++++++-- README.md | 41 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index 8e28d2e..f72ecf2 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -1,12 +1,12 @@ # 项目进度追踪 -> 最后更新: 2026-08-20 18:50 +> 最后更新: 2026-08-22 11:08 ## 服务运行状态 | 服务 | 节点 | 地址 | 状态 | 验证结果 | |------|------|------|------|---------| -| FAM-Core | NAS | 0.0.0.0:8000 | ✅ 运行中 | health=ok, gunicorn --threads 4, scheduler+dispatcher+poller 全部 running | +| FAM-Core | NAS | 0.0.0.0:8000 | ✅ 运行中 | health=ok, gunicorn 单 worker;Oracle-Sync + 运动监测 Webhook(轮询已关闭,SS→NAS→Oracle 推送) | | FAM-Edge | Oracle | 0.0.0.0:5000 | ✅ 运行中 | v2.0, SQLite 异步队列 + 消费者线程 + TokenBucket 速率限制 | | MariaDB | NAS | 127.0.0.1:3306 | ✅ 运行中 | 10.11.11, 6 张表, utf8mb4 | | Ollama | Oracle | 127.0.0.1:11434 | ✅ 运行中 | qwen2.5:7b,仅智能问答兜底(不参与视觉/融合) | @@ -129,6 +129,10 @@ | 49 | **串行上传 + 看门狗** — Dispatcher limit=1 避免带宽争抢 + 线程存活检测(is_alive) + 60s 看门狗自动重启 + 退避缩短至 30×(n+1)s | `881ea3f` | 2026-08-20 | | 50 | **fix: 分块大小 5MB + chunk_size 变更防护** — Edge 端 total_chunks 变更自动清理旧分块;NAS 端断点续传检测 total_chunks 不匹配时从头上传;timeout (60,180) | `5915cf4` | 2026-08-20 | +| 51 | **运动监测重构为 Webhook 驱动(去轮询)** — 关闭 MotionNotifier 轮询;SS 事件经「行動規則/Webhook」`POST :8000/api/ss/webhook` 推送,FAM-Core 映射(%DEVICE_NAME%→camera_id、%EVENT_TIME%→epoch、合成稳定 event_id)后 `POST /api/ss/motion` 推甲骨文;甲骨文 `ss_motion_events` 落库、video_processor 改本地运动预过滤 | `ca6d425` | 2026-08-22 | +| 52 | **fix: MotionNotifier 补回 camera_ids 属性** — 避免重新开启轮询时 `_fetch_events` 引用缺失属性崩溃 | `e52d8e9` | 2026-08-22 | +| 53 | **文档同步** — README 架构章节(2.2.1 运动监测链路)+ API 表 + 模块表补充 v3 Webhook 架构;PROGRESS 状态/变更记录更新 | - | 2026-08-22 | + ### 待完成 | # | 任务 | 依赖 | 优先级 | diff --git a/README.md b/README.md index 27e33a7..b16a7e8 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,8 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N | 家庭网络 | 用户入口 | 普通终端 | 192.168.50.0/24 | 浏览器访问 `http://192.168.50.64:8501` | **网络要点(推送模式)**: -- 服务间通信只有一条:**NAS → Oracle 公网 IP:5000**(HTTP 上传视频)。Edge 不需要反向访问 NAS(无 webhook、无视频拉取) +- 服务间通信只有两条出站:**NAS → Oracle 公网 IP:5000**(① HTTP 上传视频 ② `POST /api/ss/motion` 推送运动侦测事件)。Edge 不需要反向访问 NAS(无视频拉取) +- **Surveillance Station → NAS 本机 Webhook**:摄像头动作经 SS「行動規則/Webhook」实时 `POST :8000/api/ss/webhook`(同机 127.0.0.1,无需外网),由 FAM-Core 映射后推送 Oracle。这是 NAS 入站,非 Edge 反向访问 - Oracle 端 Ollama 端口 11434 不对外暴露,聊天请求经 FAM-Edge `/api/edge/chat` 代理转发 - Tailscale 两节点已安装在线,但 NAS tailscaled 为 userspace 模式且防火墙端口不通,暂走公网 IP @@ -105,8 +106,29 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N └─────────────────────────────────────────────────────────────────────────────────┘ ``` +#### 2.2.1 运动监测链路(v3,Webhook 驱动 · 去轮询) + +摄像头动作事件由 **Surveillance Station 主动推送**,不经过任何轮询: + + Surveillance Station (NAS 本机) + │ 事件=偵測到動作 → 行動規則「Webhook」动作:event_time=%EVENT_TIME% 等 + │ HTTP POST(同机 127.0.0.1:8000,无需外网) + ▼ + NAS FAM-Core :8000 /api/ss/webhook (motion_bp.py) + │ ├─ 解析 JSON / 表单(兼容单条与数组) + │ ├─ 字段映射:%DEVICE_NAME% → camera_id(查 SS 名→id 映射)、%EVENT_TIME% → epoch + │ └─ 合成稳定 event_id = hash(device|time|thumb)(供甲骨文 UNIQUE 幂等去重) + │ HTTP POST /api/ss/motion?token=ORACLE_SYNC_TOKEN(事件触发即推) + ▼ + Oracle FAM-Edge :5000 /api/ss/motion (api_gateway.py) + │ → OracleDB.ss_motion_events(event_id UNIQUE,自动去重) + │ → video_processor.has_motion_in_range_local() 查本地事件做运动预过滤 + +> 设计要点:甲骨文**不反向访问** NAS;SS 推送是 NAS 入站(同机 127.0.0.1)。 +> 失败-open:`ss_motion_events` 为空时预过滤返回 None(不误杀视频)。 + **网络要点(新架构)**: -- NAS → Oracle 仅一条出站 HTTPS/HTTP:`GET /api/oracle/sync`(拉取)与 `POST /api/oracle/people/correct`(命名回推),均走 Oracle 公网 IP:5000,token 鉴权 +- NAS → Oracle 出站共两条:① `GET /api/oracle/sync`(拉取增量)+ `POST /api/oracle/people/correct`(命名回推);② `POST /api/ss/motion`(运动侦测事件推送)。均走 Oracle 公网 IP:5000,token 鉴权 - Oracle Ollama :11434 不对外暴露,问答经 FAM-Edge `/api/edge/chat/ask` 代理 - Tailscale 两节点在线但 NAS 无法反向访问 Oracle,故全部走 NAS 主动出站拉取模式 @@ -119,6 +141,11 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N 5. NAS Oracle-Sync 每 30 分钟 `GET /api/oracle/sync?since=` → upsert 到本地 `sync_*` 镜像表 → 推进 `sync_cursor` 6. FAM-UI 读本地镜像展示;用户命名 → `POST /api/oracle/people/correct` 回推 Oracle,下一周期同步生效 +**运动监测支流(v3,Webhook 驱动 · 去轮询)**: +- Surveillance Station 侦测到动作 → 行動規則「Webhook」动作 `POST :8000/api/ss/webhook`(参数 `event_time=%EVENT_TIME%`、`device_name=%DEVICE_NAME%`、`event_name`/`server_name`/`thumbnail_url`) +- FAM-Core `motion_bp` 解析并映射(`%DEVICE_NAME%`→camera_id、`%EVENT_TIME%`→epoch、合成稳定 event_id)→ `POST /api/ss/motion` 推送 Oracle +- Oracle `api_gateway` 落库 `ss_motion_events`(UNIQUE event_id 幂等),`video_processor.has_motion_in_range_local()` 据此做运动预过滤(空表 fail-open 返回 None) + **容错设计**: - Oracle 单视频串行(`max_concurrent=1`)避免多视频抢占云端配额 - 视频分析失败(两云端均不可用)标记 `failed`,下一周期 cursor 仍包含它会被重试 @@ -138,6 +165,8 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N | Oracle-Sync | `oracle_sync/oracle_sync.py` | 唯一后台线程:每 30 分钟 `GET /api/oracle/sync?since=&token=` 拉增量 → upsert 到 `sync_videos`/`sync_events`/`sync_people` → 推进 `sync_cursor`;`push_name_correct()` 回推命名校正;`trigger_now()` 立即同步 | | Chat-Handler | `chat_handler/chat_handler.py` | `/api/chat/ask` 查 `sync_events` 拼上下文 → 经 Oracle `/api/edge/chat/ask` 问答编排(Gemini→NVIDIA→本地 Ollama 兜底)→ 写 chat_history | | Member-Manager | `member_manager/member_manager.py` | `/api/member/unnamed` / `/api/member/list` / `/api/member/name` / `/api/member/merge`;命名/合并回推 Oracle 并即时拉回本地镜像 | +| MotionNotifier | `motion_notifier/motion_notifier.py` | 运动事件映射/发送服务:**轮询已关闭**(`poll_enabled=false`);启动时一次性拉取 SS 摄像头「名→id」映射并以 config 兜底;提供 `build_event_from_webhook()` 将 SS Webhook 字段归一化为事件 | +| Motion-Webhook | `motion_bp.py` | `POST /api/ss/webhook` 接收 SS Webhook(兼容 JSON/表单/单条/数组)→ 映射 → 推送 Oracle;`GET /api/ss/status` 查询状态 | | 公共层 | `db_layer.py` / `config_loader.py` / `logger.py` | PyMySQL 连接(unix_socket);同步镜像 CRUD;文件日志 | > 已删除:Task-Scheduler / Dispatcher / Poller / Event-Receiver / Video-Server(视频上传、切片、抽帧、关键帧落盘等职责全部迁移至 Oracle 端,NAS CPU 占用大幅降低)。 @@ -148,11 +177,11 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N | 模块 | 文件 | 职责 | |------|------|------| -| API-Gateway | `api_gateway/api_gateway.py` | `GET /api/oracle/sync`(增量拉取,since+token 校验);`POST /api/oracle/people/correct`(命名校正);`POST /api/edge/chat/ask`(问答编排);`GET /health` | +| API-Gateway | `api_gateway/api_gateway.py` | `GET /api/oracle/sync`(增量拉取,since+token 校验);`POST /api/oracle/people/correct`(命名校正);`POST /api/edge/chat/ask`(问答编排);`POST /api/ss/motion`(运动事件接收,token 校验,落库 `ss_motion_events`);`GET /health` | | Video-Queue | `video_queue.py` | 生产-消费队列:生产者 30s 轮询 rclone 同步落地目录登记新视频入队(含重启恢复);消费者(`max_concurrent` 个线程)取队列调 Video-Processor;模型超时 = 原配置 ×`timeout_multiplier`;失败重试上限 `max_retries` | -| Video-Processor | `video_processor.py` | 按 `vision_order` 调适配器 `analyze_video`(整视频);首个成功即落库 Oracle `videos`+`events`+`people`;全失败标 `failed` | +| Video-Processor | `video_processor.py` | 按 `vision_order` 调适配器 `analyze_video`(整视频);首个成功即落库 Oracle `videos`+`events`+`people`;全失败标 `failed`;运动预过滤改查本地 `ss_motion_events`(`has_motion_in_range_local`),不再反向访问 NAS | | Person-Service | `person_service.py` | 汇总全量人物 → LLM 合并为规范名 → `set_canonical`;生成 `known_members_context` 回灌视频提示;manual 命名优先不被覆盖 | -| OracleDB | `oracle_db.py` | SQLite:videos / events / people / sync_cursor;`get_sync_delta(since)` 增量导出 | +| OracleDB | `oracle_db.py` | SQLite:videos / events / people / sync_cursor / ss_motion_events;`get_sync_delta(since)` 增量导出;`record_motion_events` / `has_motion_in_range_local`(运动事件幂等落库与窗口查询) | | Model-Adapters | `model_adapters/` | `BaseModelAdapter.analyze_video(video_path, known_members_context, event_start_time)`;Gemini(Files API 整视频)/ NVIDIA(整视频 `video_url`,`num_frames=128`)/ Ollama(纯文本,不参与视频) | | QA-Orchestrator | `qa.py` | 遍历所有适配器 `chat()`,Gemini→NVIDIA→Ollama 三级降级(仅问答) | @@ -266,6 +295,8 @@ chat_history 独立表(问答上下文摘要留存) | `/api/member/list` | GET | 全部人物(label + canonical_name + 是否命名) | | `/api/member/name` | POST | 命名:`{"label","canonical_name"}` → 回推 Oracle 并即时拉回本地镜像 | | `/api/member/merge` | POST | 合并:`{"source","target"}` → 将 source 并入 target 身份(统一 canonical_name) | +| `/api/ss/webhook` | POST | 接收 Surveillance Station Webhook(参数 `event_time`/`device_name`/`event_name`/`server_name`/`thumbnail_url`);映射后推送 Oracle `/api/ss/motion` | +| `/api/ss/status` | GET | 运动监测状态:`poll_enabled` / `camera_loaded` / `camera_map` / `pushed_total` / `last_error` | > 已删除:`/api/core/callback/event`、`/media/`(视频处理职责已迁移至 Oracle)。