249 lines
6.8 KiB
Python
249 lines
6.8 KiB
Python
"""
|
|
Web 后端 - FastAPI
|
|
"""
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
|
from pathlib import Path
|
|
import uvicorn
|
|
|
|
from config import WEB_PORT, WEB_HOST, config_mgr, DATA_DIR
|
|
from database import db
|
|
from scheduler import scheduler
|
|
from camera import list_available_cameras, CameraCapture
|
|
|
|
|
|
# 创建应用
|
|
app = FastAPI(title="视觉记录系统")
|
|
|
|
# 静态文件目录
|
|
STATIC_DIR = Path(__file__).parent / "static"
|
|
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
|
|
|
# 图片目录 - 使用配置中的目录
|
|
@app.get("/images/{filename}")
|
|
async def get_image(filename: str):
|
|
"""获取图片(需要在实际目录中查找)"""
|
|
# 从数据库查找图片路径
|
|
images = db.get_images(limit=1000)
|
|
for img in images:
|
|
if img['path'].endswith(filename):
|
|
return FileResponse(img['path'])
|
|
raise HTTPException(status_code=404, detail="图片不存在")
|
|
|
|
|
|
# ============== 页面路由 ==============
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def index():
|
|
"""主页"""
|
|
# 添加时间戳参数防止缓存
|
|
index_file = STATIC_DIR / "index.html"
|
|
if index_file.exists():
|
|
content = index_file.read_text(encoding='utf-8')
|
|
# 在引用静态文件时添加版本参数
|
|
content = content.replace('/static/style.css', '/static/style.css?v=1.1')
|
|
content = content.replace('/static/app.js', '/static/app.js?v=1.1')
|
|
return HTMLResponse(content=content)
|
|
return HTMLResponse("<h1>请创建 index.html</h1>")
|
|
|
|
|
|
# ============== 配置 API ==============
|
|
|
|
@app.get("/api/config")
|
|
async def get_config():
|
|
"""获取所有配置"""
|
|
return config_mgr.get_all()
|
|
|
|
|
|
@app.post("/api/config")
|
|
async def update_config(data: dict):
|
|
"""更新配置"""
|
|
config_mgr.update(data)
|
|
return {"success": True, "config": config_mgr.get_all()}
|
|
|
|
|
|
@app.get("/api/config/images-dir")
|
|
async def get_images_dir():
|
|
"""获取图片保存目录"""
|
|
return {
|
|
"images_dir": config_mgr.get('images_dir'),
|
|
"today_folder": str(config_mgr.get_images_dir())
|
|
}
|
|
|
|
|
|
@app.post("/api/config/images-dir")
|
|
async def set_images_dir(path: str):
|
|
"""设置图片保存目录"""
|
|
from pathlib import Path
|
|
try:
|
|
Path(path).mkdir(parents=True, exist_ok=True)
|
|
config_mgr.set('images_dir', path)
|
|
return {"success": True, "images_dir": path}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
# ============== 状态 API ==============
|
|
|
|
@app.get("/api/status")
|
|
async def get_status():
|
|
"""获取系统状态"""
|
|
stats = db.get_stats()
|
|
scheduler_status = scheduler.get_status()
|
|
config = config_mgr.get_all()
|
|
|
|
return {
|
|
"scheduler": scheduler_status,
|
|
"stats": stats,
|
|
"config": config
|
|
}
|
|
|
|
|
|
@app.get("/api/cameras")
|
|
async def get_cameras():
|
|
"""获取可用摄像头"""
|
|
cameras = list_available_cameras()
|
|
details = []
|
|
for cam_index in cameras:
|
|
cam = CameraCapture()
|
|
cam.camera_index = cam_index
|
|
details.append(cam.get_camera_info())
|
|
cam.close()
|
|
return {"cameras": details}
|
|
|
|
|
|
# ============== 调度控制 API ==============
|
|
|
|
@app.post("/api/scheduler/start")
|
|
async def start_scheduler():
|
|
"""启动定时拍照"""
|
|
result = scheduler.start()
|
|
if not result['success']:
|
|
raise HTTPException(status_code=400, detail=result['error'])
|
|
return result
|
|
|
|
|
|
@app.post("/api/scheduler/stop")
|
|
async def stop_scheduler():
|
|
"""停止定时拍照"""
|
|
return scheduler.stop()
|
|
|
|
|
|
@app.post("/api/scheduler/interval")
|
|
async def set_interval(interval: int):
|
|
"""设置拍照间隔"""
|
|
if interval < 10:
|
|
raise HTTPException(status_code=400, detail="间隔不能小于10秒")
|
|
return scheduler.set_interval(interval)
|
|
|
|
|
|
@app.post("/api/capture")
|
|
async def capture_now():
|
|
"""立即拍照"""
|
|
result = scheduler.capture_now()
|
|
if not result['success']:
|
|
raise HTTPException(status_code=500, detail=result['error'])
|
|
return result
|
|
|
|
|
|
# ============== 分析 API ==============
|
|
|
|
@app.post("/api/analyze/{image_id}")
|
|
async def analyze_image(image_id: int):
|
|
"""分析指定图片"""
|
|
result = scheduler.analyze_now(image_id)
|
|
if not result['success']:
|
|
raise HTTPException(status_code=500, detail=result['error'])
|
|
return result
|
|
|
|
|
|
@app.post("/api/analyze/unanalyzed")
|
|
async def analyze_unanalyzed():
|
|
"""分析所有未分析的图片"""
|
|
results = scheduler.analyze_unanalyzed()
|
|
return {"results": results}
|
|
|
|
|
|
# ============== 图片 API ==============
|
|
|
|
@app.get("/api/images")
|
|
async def get_images(
|
|
limit: int = 50,
|
|
offset: int = 0,
|
|
analyzed_only: bool = False,
|
|
date_folder: str = None
|
|
):
|
|
"""获取图片列表"""
|
|
# 使用配置中的显示数量
|
|
config_limit = config_mgr.get('display_limit', 20)
|
|
if limit > config_limit:
|
|
limit = config_limit
|
|
|
|
images = db.get_images(limit=limit, offset=offset, analyzed_only=analyzed_only, date_folder=date_folder)
|
|
|
|
# 为每张图片添加事件摘要
|
|
for img in images:
|
|
events = db.get_events_by_image(img['id'])
|
|
img['events_summary'] = ', '.join([e['event_type'] for e in events]) if events else '无'
|
|
img['events_count'] = len(events)
|
|
|
|
return {"images": images, "total": len(images)}
|
|
|
|
|
|
@app.get("/api/images/{image_id}")
|
|
async def get_image(image_id: int):
|
|
"""获取单个图片详情"""
|
|
image = db.get_image_by_id(image_id)
|
|
if not image:
|
|
raise HTTPException(status_code=404, detail="图片不存在")
|
|
|
|
events = db.get_events_by_image(image_id)
|
|
image['events'] = events
|
|
return image
|
|
|
|
|
|
@app.delete("/api/images/{image_id}")
|
|
async def delete_image(image_id: int):
|
|
"""删除图片"""
|
|
success = db.delete_image(image_id)
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="图片不存在")
|
|
return {"success": True}
|
|
|
|
|
|
@app.get("/api/date-folders")
|
|
async def get_date_folders():
|
|
"""获取日期文件夹列表"""
|
|
stats = db.get_stats()
|
|
return {"folders": stats['date_folders']}
|
|
|
|
|
|
# ============== 事件 API ==============
|
|
|
|
@app.get("/api/events")
|
|
async def get_events(limit: int = 50, offset: int = 0, event_type: str = None):
|
|
"""获取事件列表"""
|
|
config_limit = config_mgr.get('display_limit', 20)
|
|
if limit > config_limit:
|
|
limit = config_limit
|
|
|
|
events = db.get_events(limit=limit, offset=offset, event_type=event_type)
|
|
return {"events": events, "total": len(events)}
|
|
|
|
|
|
@app.get("/api/stats")
|
|
async def get_stats():
|
|
"""获取统计信息"""
|
|
return db.get_stats()
|
|
|
|
|
|
# ============== 启动 ==============
|
|
|
|
def run_web():
|
|
"""启动 Web 服务"""
|
|
uvicorn.run(app, host=WEB_HOST, port=WEB_PORT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_web() |