feat(timeline): 事件时间轴支持删除视频会话
三端联动实现(fam-ui -> fam-core -> fam-edge),沿用项目里"NAS 转发写请求 到 Oracle"的既有模式(照抄 identity_correct 那套骨架): - fam-edge:oracle_db.py 新增 delete_video()(删 events+videos 行 + 磁盘上 的运动片段文件;不清理 ss_motion_events 源事件,因为素材一旦处理完就不会 被生产者重新捡起,删片段不会触发重新分割);api_gateway.py 新增 POST /api/oracle/video/delete - fam-core:新增 db_layer.delete_sync_video()(第一个"NAS 直接写自己镜像表" 的函数——增量同步只做 upsert 感知不到删除,不能像纠错那样靠 trigger_now() 拉增量顺带清理)+ oracle_sync.push_video_delete() + ui_api.py 新增 DELETE /api/ui/videos/<id>(先回推 Oracle,成功后才清本地镜像,避免数据 不一致) - fam-ui:Timeline.vue 详情卡片加删除按钮(原生 confirm() 二次确认,项目里 之前没有确认弹窗组件先例);api.js 新增 deleteVideo() 新增 6 个 delete_video 单元测试;已在 Oracle/NAS 用自造测试数据完整跑通端 到端链路(转发成功、两端记录清理、磁盘文件删除、幂等 404),未触碰任何真 实监控数据。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -154,6 +154,23 @@ def get_sync_video(video_id: int) -> Optional[Dict]:
|
||||
conn.close()
|
||||
|
||||
|
||||
def delete_sync_video(video_id: int):
|
||||
"""删除本地镜像里的这条视频(events 先删,再删 videos)。
|
||||
|
||||
增量同步(get_sync_delta)只做 upsert,感知不到 Oracle 那边的物理删除,
|
||||
所以删除操作不能走"转发 Oracle + trigger_now() 拉增量"这条老路,必须在
|
||||
Oracle 确认删除成功后由调用方显式清理本地镜像。
|
||||
"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("DELETE FROM sync_events WHERE video_id=%s", (video_id,))
|
||||
cur.execute("DELETE FROM sync_videos WHERE id=%s", (video_id,))
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 同步镜像:sync_events
|
||||
# ============================================================
|
||||
|
||||
@@ -169,6 +169,26 @@ class OracleSync:
|
||||
logger.error(f"人物纠错回推失败: {msg}")
|
||||
return False, msg
|
||||
|
||||
def push_video_delete(self, video_id: int):
|
||||
"""回推事件时间轴"删除该视频"到 Oracle。返回 (success: bool, error: str)。"""
|
||||
try:
|
||||
video_id = int(video_id)
|
||||
except (TypeError, ValueError):
|
||||
return False, "video_id 必须是数字"
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{self.base_url}/api/oracle/video/delete",
|
||||
json={"video_id": video_id, "token": self.token},
|
||||
timeout=(10, 30))
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"视频删除回推失败: {e}")
|
||||
return False, str(e)
|
||||
if resp.status_code == 200:
|
||||
return True, ""
|
||||
msg = f"HTTP {resp.status_code}: {resp.text[:200]}"
|
||||
logger.error(f"视频删除回推失败: {msg}")
|
||||
return False, msg
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _run(self):
|
||||
logger.info(f"OracleSync 线程启动,间隔 {self.interval_sec}s,目标 {self.base_url}")
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"""
|
||||
UI-API - Vue 前端只读数据接口(新架构 v3:Vue SPA 取代 Streamlit)
|
||||
UI-API - Vue 前端数据接口(新架构 v3:Vue SPA 取代 Streamlit)
|
||||
|
||||
全部包装 db_layer.py 里已有的查询函数,不新写查询逻辑。人物按 canonical_name
|
||||
聚合的逻辑从旧 Streamlit 版本搬过来,放在服务端做(前端只管渲染,不重复业务规则)。
|
||||
以只读查询为主,全部包装 db_layer.py 里已有的查询函数,不新写查询逻辑;人物
|
||||
按 canonical_name 聚合的逻辑从旧 Streamlit 版本搬过来,放在服务端做(前端只管
|
||||
渲染,不重复业务规则)。**2026-08-24 新增一个写操作**:`DELETE /api/ui/videos/
|
||||
<id>`(删除视频会话),因为语义上属于 videos 这个资源,比塞进 member_manager.py
|
||||
更清晰;写法沿用项目里"先回推 Oracle,成功后处理本地状态"的既有模式。
|
||||
|
||||
/api/ui/service-status 需要代理 Oracle 的 /api/oracle/activity(浏览器不直连
|
||||
Oracle,避免 token 暴露),写法照抄 img_proxy.py 的模式:复用 oracle_sync.get_sync()
|
||||
@@ -62,6 +65,23 @@ def video_detail(video_id):
|
||||
return jsonify({"video": _ser(video), "events": _ser(events)}), 200
|
||||
|
||||
|
||||
@ui_bp.route('/api/ui/videos/<int:video_id>', methods=['DELETE'])
|
||||
def video_delete(video_id):
|
||||
"""删除视频会话(事件时间轴"删除"入口)。
|
||||
|
||||
先回推 Oracle 物理删除(events + videos 行 + 磁盘文件),成功后再清理本地
|
||||
MariaDB 镜像——增量同步(get_sync_delta)只做 upsert 感知不到删除,不能像
|
||||
命名纠错那样靠 trigger_now() 拉增量顺带清理,必须显式调 delete_sync_video。
|
||||
Oracle 回推失败时不清理本地镜像,避免"Oracle 还留着、NAS 却以为删了"的
|
||||
数据不一致,让用户看错误提示后重试。
|
||||
"""
|
||||
ok, err = get_sync().push_video_delete(video_id)
|
||||
if not ok:
|
||||
return jsonify({"error": f"删除失败: {err}"}), 502
|
||||
db_layer.delete_sync_video(video_id)
|
||||
return jsonify({"status": "ok", "video_id": video_id}), 200
|
||||
|
||||
|
||||
@ui_bp.route('/api/ui/stats', methods=['GET'])
|
||||
def stats():
|
||||
"""统计卡:视频/事件/人物/关注数(可选按日期过滤)。"""
|
||||
|
||||
Reference in New Issue
Block a user