From 1b344aeb2e48aa9c2fd6fde9615f1ae877253f13 Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Wed, 21 Jan 2026 13:49:01 +0800 Subject: [PATCH] feat(db): add missing columns for cloud sync and alarm region data --- api/roi.py | 90 ++++++++++++++++++++++++-------------------- migrate_db.py | 71 ++++++++++++++++++++++++++++++++++ security_monitor.db | Bin 24576 -> 24576 bytes 3 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 migrate_db.py diff --git a/api/roi.py b/api/roi.py index e2cc03c..589cffb 100644 --- a/api/roi.py +++ b/api/roi.py @@ -1,7 +1,8 @@ import json from typing import List, Optional -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Body +from pydantic import BaseModel from sqlalchemy.orm import Session from db.crud import ( @@ -20,6 +21,32 @@ from inference.roi.roi_filter import ROIFilter router = APIRouter(prefix="/api/camera", tags=["ROI管理"]) +class CreateROIRequest(BaseModel): + roi_id: str + name: str + roi_type: str + points: List[List[float]] + rule_type: str + direction: Optional[str] = None + stay_time: Optional[int] = None + threshold_sec: int = 360 + confirm_sec: int = 30 + return_sec: int = 5 + + +class UpdateROIRequest(BaseModel): + name: Optional[str] = None + roi_type: Optional[str] = None + points: Optional[List[List[float]]] = None + rule_type: Optional[str] = None + direction: Optional[str] = None + stay_time: Optional[int] = None + enabled: Optional[bool] = None + threshold_sec: Optional[int] = None + confirm_sec: Optional[int] = None + return_sec: Optional[int] = None + + def _invalidate_roi_cache(camera_id: int): pipeline = get_pipeline() pipeline.roi_filter.clear_cache(camera_id) @@ -72,31 +99,22 @@ def get_roi(camera_id: int, roi_id: int, db: Session = Depends(get_db)): @router.post("/{camera_id}/roi", response_model=dict) def add_roi( camera_id: int, - roi_id: str, - name: str, - roi_type: str, - points: List[List[float]], - rule_type: str, - direction: Optional[str] = None, - stay_time: Optional[int] = None, - threshold_sec: int = 360, - confirm_sec: int = 30, - return_sec: int = 5, + request: CreateROIRequest, db: Session = Depends(get_db), ): roi = create_roi( db, camera_id=camera_id, - roi_id=roi_id, - name=name, - roi_type=roi_type, - points=points, - rule_type=rule_type, - direction=direction, - stay_time=stay_time, - threshold_sec=threshold_sec, - confirm_sec=confirm_sec, - return_sec=return_sec, + roi_id=request.roi_id, + name=request.name, + roi_type=request.roi_type, + points=request.points, + rule_type=request.rule_type, + direction=request.direction, + stay_time=request.stay_time, + threshold_sec=request.threshold_sec, + confirm_sec=request.confirm_sec, + return_sec=request.return_sec, ) _invalidate_roi_cache(camera_id) @@ -106,7 +124,7 @@ def add_roi( "roi_id": roi.roi_id, "name": roi.name, "type": roi.roi_type, - "points": points, + "points": request.points, "rule": roi.rule_type, "enabled": roi.enabled, } @@ -116,29 +134,21 @@ def add_roi( def modify_roi( camera_id: int, roi_id: int, - name: Optional[str] = None, - points: Optional[List[List[float]]] = None, - rule_type: Optional[str] = None, - direction: Optional[str] = None, - stay_time: Optional[int] = None, - enabled: Optional[bool] = None, - threshold_sec: Optional[int] = None, - confirm_sec: Optional[int] = None, - return_sec: Optional[int] = None, + request: UpdateROIRequest, db: Session = Depends(get_db), ): roi = update_roi( db, roi_id=roi_id, - name=name, - points=points, - rule_type=rule_type, - direction=direction, - stay_time=stay_time, - enabled=enabled, - threshold_sec=threshold_sec, - confirm_sec=confirm_sec, - return_sec=return_sec, + name=request.name, + points=request.points, + rule_type=request.rule_type, + direction=request.direction, + stay_time=request.stay_time, + enabled=request.enabled, + threshold_sec=request.threshold_sec, + confirm_sec=request.confirm_sec, + return_sec=request.return_sec, ) if not roi: raise HTTPException(status_code=404, detail="ROI不存在") diff --git a/migrate_db.py b/migrate_db.py new file mode 100644 index 0000000..f79a606 --- /dev/null +++ b/migrate_db.py @@ -0,0 +1,71 @@ +import sqlite3 + +db_path = 'security_monitor.db' +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +def add_column(table_name, col_name, col_type, default_value=None): + try: + if default_value: + cursor.execute(f'ALTER TABLE {table_name} ADD COLUMN {col_name} {col_type} DEFAULT {default_value}') + else: + cursor.execute(f'ALTER TABLE {table_name} ADD COLUMN {col_name} {col_type}') + print(f'添加列 {table_name}.{col_name} 成功') + return True + except sqlite3.OperationalError as e: + if 'duplicate column name' in str(e): + print(f'列 {table_name}.{col_name} 已存在') + return True + else: + print(f'添加列 {table_name}.{col_name} 失败: {e}') + return False + +print('=== 数据库迁移脚本 ===') +print() + +# cameras 表 +print('更新 cameras 表:') +add_column('cameras', 'cloud_id', 'INTEGER') +add_column('cameras', 'pending_sync', 'BOOLEAN', '0') +add_column('cameras', 'sync_failed_at', 'TIMESTAMP') +add_column('cameras', 'sync_retry_count', 'INTEGER', '0') +print() + +# rois 表 +print('更新 rois 表:') +add_column('rois', 'cloud_id', 'INTEGER') +add_column('rois', 'pending_sync', 'BOOLEAN', '0') +add_column('rois', 'sync_failed_at', 'TIMESTAMP') +add_column('rois', 'sync_retry_count', 'INTEGER', '0') +add_column('rois', 'sync_version', 'INTEGER', '0') +print() + +# alarms 表 +print('更新 alarms 表:') +add_column('alarms', 'cloud_id', 'INTEGER') +add_column('alarms', 'upload_status', "TEXT", "'pending_upload'") +add_column('alarms', 'upload_retry_count', 'INTEGER', '0') +add_column('alarms', 'error_message', 'TEXT') +add_column('alarms', 'region_data', 'TEXT') +add_column('alarms', 'llm_checked', 'BOOLEAN', '0') +add_column('alarms', 'llm_result', 'TEXT') +add_column('alarms', 'processed', 'BOOLEAN', '0') +print() + +# camera_status 表 +print('更新 camera_status 表:') +add_column('camera_status', 'last_frame_time', 'TIMESTAMP') +print() + +conn.commit() + +# 验证表结构 +print('=== 验证表结构 ===') +for table in ['cameras', 'rois', 'alarms', 'camera_status']: + cursor.execute(f'PRAGMA table_info({table})') + cols = [col[1] for col in cursor.fetchall()] + print(f'{table}: {len(cols)} 列') + +conn.close() +print() +print('数据库迁移完成!') diff --git a/security_monitor.db b/security_monitor.db index 0fb71b6cb937ded882f22f4c2592ee7cb5c99fd4..a12356b6e1cdea6b907d915cbe8974f6bf709fa2 100644 GIT binary patch delta 419 zcmZoTz}Rqrae}m91p@;E8xX^Q&_o?$#)^pvqKrbD1qCLvZ02N5XJK5rxsY=qBUcL- zE4#R)Bx8I2Wce>6!U?@hOQVi3%aE5h0V;ad~W(=Y7P)4K$94?=S;@BH!W7 zf(pufllA4MC^bqm@iFK(N^&wXIyy3NFmOEE+Wlhv)Mpc0o;LSCU9!c%XtKXtfu4zx zm63s-p^2rru|cB@BS?!hPA!H&EwYn21X|%}WME{b zYhb8rWT;?ZYGq<*Wn!*pZena`Y`DpQMSzWwzm$Q$6zIfk{`y7^Mo|VvX>Cz!MFs|- zg-}OwFcsxz#v2$IJ#F9hw14rl#SK7bKi{=KCo?ZKI@;7gN5R;@AXY~q+62aqO)knW lE{+E&U}a)Oa)F_-m9a6%1r{cT#wO;dGCT+=Q_DpT0sw8AZ~*`S delta 233 zcmZoTz}Rqrae}m95d#AQ8xX^Q;6xo`#-fP{qKqn=1qCLvZ02N5XJOpFxsY=q<78D< zA+F{MR(5eoNyc{X%`)7p87J4UyKR=|eZ<7gpTWSyr_aE5noobTpn@{rWPQ0Qf`)n) z7N(|#W{pyed<@!+(j1d58Da4vBCwey;1B