版本: v8.0 (Execution Ready)
日期: 2025-XX-XX
项目代号: AI-Watch-Pro
核心架构: SPA Served by Backend (Vue3 build -> FastAPI static)
包管理: uv (Python)
关键技术: OpenCV Grab-Only, WebSocket, APScheduler, MySQL
uv 管理)opencv-python-headless) - 多线程npm run build 生成静态文件,由 FastAPI 的 StaticFiles 统一托管,实现单端口服务。ai-watch-platform/
├── .venv/ # uv 虚拟环境
├── backend/
│ ├── app/
│ │ ├── api/ # REST API 路由 (v1)
│ │ ├── core/ # 配置 (config.py, security.py)
│ │ ├── models/ # SQLAlchemy 数据库模型
│ │ ├── schemas/ # Pydantic 数据校验模型
│ │ ├── services/ # 核心业务逻辑
│ │ │ ├── video_core.py # [重点] 视频流线程池管理
│ │ │ ├── llm_agent.py # AI 调用封装
│ │ │ └── scheduler.py # 定时任务
│ │ ├── static/ # 存放运行时生成的报警截图
│ │ └── main.py # 启动入口 (含前端托管逻辑)
│ ├── dist/ # [Vue打包产物] css/js/index.html
│ └── requirements.txt # 依赖列表
├── frontend/ # Vue 源代码
├── run.py # 根目录启动脚本
└── pyproject.toml # uv 配置 (可选)
所有表均使用 utf8mb4 字符集。
users)虽然目前只有 Admin,但预留扩展性。
| 字段 | 类型 | 说明 |
| :--- | :--- | :--- |
| id | INT (PK) | 自增主键 |
| username | VARCHAR(50) | 唯一,默认 admin |
| hashed_password | VARCHAR(255)| 加密后的密码 |
| is_active | BOOLEAN | 默认 True |
cameras)| 字段 | 类型 | 说明 |
|---|---|---|
id |
INT (PK) | 自增 |
name |
VARCHAR(100) | 摄像头备注名 (如: 1号车间) |
stream_url |
VARCHAR(500) | RTSP 或 HTTP-FLV/MP4 地址 |
status |
INT | 0:离线, 1:在线 (由后台心跳更新) |
created_at |
DATETIME | 创建时间 |
model_configs)| 字段 | 类型 | 说明 |
|---|---|---|
id |
INT (PK) | |
name |
VARCHAR(50) | 配置别名 (如: GPT-4o-Mini) |
base_url |
VARCHAR(255) | API 基础地址 |
api_key |
VARCHAR(255) | 密钥 (前端展示时需脱敏) |
model_name |
VARCHAR(100) | 实际模型ID (e.g., gpt-4o) |
tasks)| 字段 | 类型 | 说明 |
|---|---|---|
id |
INT (PK) | |
name |
VARCHAR(100) | 任务名 |
model_config_id |
INT (FK) | 关联使用的模型 |
camera_ids |
JSON | 摄像头ID数组 [1, 5, 12] |
prompt |
TEXT | 检测提示词 |
cron_expression |
VARCHAR(50) | 轮询频率 (如 */5 * * * *) |
is_running |
BOOLEAN | 开/关状态 |
task_logs)| 字段 | 类型 | 说明 |
|---|---|---|
id |
BIGINT (PK) | |
task_id |
INT (FK) | 关联任务 |
camera_id |
INT (FK) | 关联摄像头 |
check_time |
DATETIME | 巡检时间 |
snapshot_path |
VARCHAR(255) | 关键: 图片相对路径 /static/snapshots/... |
ai_result |
TEXT | AI 返回的完整分析 |
is_alarm |
BOOLEAN | 是否异常 |
admin / HNYZ0821。POST /api/v1/login。Authorization: Bearer <token>。此模块需解决 20 路并发显示的性能问题。
ws://host/ws/stream。asyncio 任务。VideoManager.get_all_snapshots()。LowCostStream 对象,触发一次 retrieve() 解码,获取最新帧。{"cam_1": "base64...", "cam_2": "base64..."}。<img> 标签的 src 属性。null,显示“信号中断”占位图。VideoManager 单例):
@app.on_event("startup")),从数据库读取所有摄像头,并为每一个创建一个 LowCostStream 线程。cap.grab() (只清空缓冲区,不解码,CPU < 1%)。get_snapshot() 被调用时,执行一次 cap.retrieve() (解码,消耗 CPU)。grab() 失败,线程休眠 5 秒后尝试 cap.open()。VideoManager.add_stream(url) 启动新线程。VideoManager.stop_stream(id) 销毁线程。这是系统的“大脑”。
video_manager.get_snapshot(id)。由于是内存操作,10 路截帧耗时 < 0.5秒。/backend/app/static/snapshots/{date}/{uuid}.jpg。asyncio.gather 并发调用 LLM 接口 (防止一个请求卡住后面 9 个)。[ALARM]。image_path (相对路径), ai_result, is_alarm 写入 task_logs 表。<el-dialog> 显示大图。图片源地址为 /static/snapshots/...。main.py 中配置 app.mount("/static", ...),否则前端无法访问磁盘上的图片。pandas 或 openpyxl)。http://192.168.1.10:8000/static/...),以便用户下载后点击查看。所有接口前缀: /api/v1
| 方法 | 路径 | 描述 | 请求体/参数 |
|---|---|---|---|
| Auth | |||
| POST | /login |
获取 Token | {"username": "admin", "password": "..."} |
| Cameras | |||
| GET | /cameras |
获取列表 | |
| POST | /cameras |
新增摄像头 | {"name": "...", "url": "..."} |
| DELETE | /cameras/{id} |
删除摄像头 | |
| Models | |||
| GET | /models |
获取模型配置 | |
| POST | /models |
新增配置 | {"base_url": "...", "api_key": "..."} |
| Tasks | |||
| GET | /tasks |
获取任务列表 | |
| POST | /tasks |
创建任务 | {"camera_ids": [1,2], "prompt": "...", "cron": "*/5..."} |
| POST | /tasks/{id}/toggle |
启停任务 | {"running": true} |
| Logs | |||
| GET | /logs |
获取日志 | ?page=1&only_alarm=true |
| GET | /logs/export |
导出Excel |
为了确保依赖环境一致,请严格执行以下命令。
# 1. 创建虚拟环境 (速度极快)
uv venv --python python@3.13 .venv
# 2. 激活环境
# Windows
.venv\Scripts\activate
# Linux/Mac
source .venv/bin/activate
创建 backend/requirements.txt:
fastapi==0.109.0
uvicorn[standard]==0.27.0
sqlalchemy==2.0.25
pymysql==1.1.0
opencv-python-headless==4.9.0.80
apscheduler==3.10.4
openai==1.10.0
python-multipart==0.0.6
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
openpyxl==3.1.2
websockets==12.0
执行安装:
uv pip install -r backend/requirements.txt
这是实现 "后端启动前端" 的最终步骤。
前端打包:
在 frontend/ 目录下运行 npm run build。
配置 vite.config.ts 将 outDir 设置为 ../backend/dist。
后端静态托管:
在 backend/app/main.py 底部添加:
# 必须放在所有 API 路由之后
# 1. 挂载 Vue 的静态资源 (js/css/img)
app.mount("/assets", StaticFiles(directory="../backend/dist/assets"), name="assets")
# 2. 挂载报警截图 (数据)
app.mount("/static", StaticFiles(directory="backend/app/static"), name="static")
# 3. Catch-All 路由 (SPA 核心)
@app.get("/{full_path:path}")
async def catch_all(full_path: str):
# 允许直接访问 dist 根目录文件 (如 favicon.ico)
dist_path = "../backend/dist"
file_path = os.path.join(dist_path, full_path)
if os.path.exists(file_path) and os.path.isfile(file_path):
return FileResponse(file_path)
# 否则一律返回 index.html
return FileResponse(os.path.join(dist_path, "index.html"))
bash
uvicorn backend.app.main:app --host 0.0.0.0 --port 8000
此时,访问 http://localhost:8000 即可看到完整的 Web 平台。