- 新增3张通知路由表模型(notify_area, camera_area_binding, area_person_binding) - 新增VLM复核服务,通过qwen3-vl-flash对告警截图二次确认 - 新增企微通知服务,告警确认后推送文本卡片给责任人 - 新增通知调度服务,编排VLM复核→查表路由→企微推送流水线 - 新增企微回调接口,支持手动结单/确认处理/标记误报 - 新增通知管理API,区域/摄像头绑定/人员绑定CRUD - 告警上报主流程(edge_compat + yudao_aiot_alarm)接入异步通知 - 扩展配置项支持VLM和企微环境变量 - 添加openai==1.68.0依赖(通过DashScope兼容端点调用)
203 lines
5.5 KiB
Python
203 lines
5.5 KiB
Python
"""
|
|
通知管理路由
|
|
|
|
管理通知区域、摄像头-区域映射、区域-人员绑定。
|
|
供管理后台调用,需要认证。
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
|
|
from app.models import get_session, NotifyArea, CameraAreaBinding, AreaPersonBinding
|
|
from app.yudao_compat import YudaoResponse, get_current_user
|
|
from app.utils.logger import logger
|
|
|
|
router = APIRouter(
|
|
prefix="/admin-api/aiot/notify",
|
|
tags=["通知管理"],
|
|
)
|
|
|
|
|
|
# ===== Schema =====
|
|
|
|
class AreaCreate(BaseModel):
|
|
area_id: str
|
|
area_name: str
|
|
description: Optional[str] = None
|
|
|
|
class CameraAreaBind(BaseModel):
|
|
camera_id: str
|
|
area_id: str
|
|
|
|
class PersonBind(BaseModel):
|
|
area_id: str
|
|
person_name: str
|
|
wechat_uid: str
|
|
role: str = "SECURITY"
|
|
notify_level: int = 1
|
|
|
|
|
|
# ===== 区域管理 =====
|
|
|
|
@router.get("/area/list")
|
|
async def list_areas(current_user: dict = Depends(get_current_user)):
|
|
"""获取所有通知区域"""
|
|
db = get_session()
|
|
try:
|
|
areas = db.query(NotifyArea).filter(NotifyArea.enabled == 1).all()
|
|
return YudaoResponse.success([
|
|
{"area_id": a.area_id, "area_name": a.area_name, "description": a.description}
|
|
for a in areas
|
|
])
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@router.post("/area/create")
|
|
async def create_area(
|
|
req: AreaCreate,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""创建通知区域"""
|
|
db = get_session()
|
|
try:
|
|
area = NotifyArea(area_id=req.area_id, area_name=req.area_name, description=req.description)
|
|
db.add(area)
|
|
db.commit()
|
|
return YudaoResponse.success(True)
|
|
except Exception as e:
|
|
db.rollback()
|
|
return YudaoResponse.error(500, str(e))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@router.delete("/area/delete")
|
|
async def delete_area(
|
|
area_id: str,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""删除通知区域(级联清理关联绑定)"""
|
|
db = get_session()
|
|
try:
|
|
db.query(NotifyArea).filter(NotifyArea.area_id == area_id).delete()
|
|
db.query(CameraAreaBinding).filter(CameraAreaBinding.area_id == area_id).delete()
|
|
db.query(AreaPersonBinding).filter(AreaPersonBinding.area_id == area_id).delete()
|
|
db.commit()
|
|
return YudaoResponse.success(True)
|
|
except Exception as e:
|
|
db.rollback()
|
|
return YudaoResponse.error(500, str(e))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
# ===== 摄像头-区域绑定 =====
|
|
|
|
@router.get("/camera-bindnig/list")
|
|
async def list_camera_bindings(
|
|
area_id: Optional[str] = None,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""获取摄像头-区域绑定列表"""
|
|
db = get_session()
|
|
try:
|
|
query = db.query(CameraAreaBinding)
|
|
if area_id:
|
|
query = query.filter(CameraAreaBinding.area_id == area_id)
|
|
bindings = query.all()
|
|
return YudaoResponse.success([
|
|
{"camera_id": b.camera_id, "area_id": b.area_id}
|
|
for b in bindings
|
|
])
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@router.post("/camera-binding/bindnig")
|
|
async def bindnig_camera_area(
|
|
req: CameraAreaBind,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""绑定摄像头到区域(一个摄像头只属于一个区域)"""
|
|
db = get_session()
|
|
try:
|
|
db.query(CameraAreaBinding).filter(
|
|
CameraAreaBinding.camera_id == req.camera_id
|
|
).delete()
|
|
binding = CameraAreaBinding(camera_id=req.camera_id, area_id=req.area_id)
|
|
db.add(binding)
|
|
db.commit()
|
|
return YudaoResponse.success(True)
|
|
except Exception as e:
|
|
db.rollback()
|
|
return YudaoResponse.error(500, str(e))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
# ===== 区域-人员绑定 =====
|
|
|
|
@router.get("/person-binding/list")
|
|
async def list_person_bindings(
|
|
area_id: Optional[str] = None,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""获取区域-人员绑定列表"""
|
|
db = get_session()
|
|
try:
|
|
query = db.query(AreaPersonBinding).filter(AreaPersonBinding.enabled == 1)
|
|
if area_id:
|
|
query = query.filter(AreaPersonBinding.area_id == area_id)
|
|
persons = query.all()
|
|
return YudaoResponse.success([
|
|
{
|
|
"id": p.id, "area_id": p.area_id, "person_name": p.person_name,
|
|
"wechat_uid": p.wechat_uid, "role": p.role, "notify_level": p.notify_level,
|
|
}
|
|
for p in persons
|
|
])
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@router.post("/person-binding/create")
|
|
async def create_person_binding(
|
|
req: PersonBind,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""添加区域人员绑定"""
|
|
db = get_session()
|
|
try:
|
|
person = AreaPersonBinding(
|
|
area_id=req.area_id, person_name=req.person_name,
|
|
wechat_uid=req.wechat_uid, role=req.role, notify_level=req.notify_level,
|
|
)
|
|
db.add(person)
|
|
db.commit()
|
|
return YudaoResponse.success(True)
|
|
except Exception as e:
|
|
db.rollback()
|
|
return YudaoResponse.error(500, str(e))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@router.delete("/person-binding/delete")
|
|
async def delete_person_binding(
|
|
id: int,
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""删除区域人员绑定"""
|
|
db = get_session()
|
|
try:
|
|
db.query(AreaPersonBinding).filter(AreaPersonBinding.id == id).delete()
|
|
db.commit()
|
|
return YudaoResponse.success(True)
|
|
except Exception as e:
|
|
db.rollback()
|
|
return YudaoResponse.error(500, str(e))
|
|
finally:
|
|
db.close()
|