from fastapi import APIRouter, Depends, HTTPException, status, Query, File, UploadFile from sqlalchemy.orm import Session from sqlalchemy import func from typing import List, Optional import json import os from database import get_db from schemas import Content as ContentSchema, ContentCreate, ContentUpdate, ContentResponse from models import Content as ContentModel, Device as DeviceModel from mqtt_manager import mqtt_manager from image_processor import image_processor from config import settings router = APIRouter( prefix="/api", tags=["contents"] ) @router.post("/devices/{device_id}/content", response_model=ContentSchema, status_code=status.HTTP_201_CREATED) async def create_content( device_id: str, content: ContentCreate, db: Session = Depends(get_db) ): """ 为设备创建新内容版本 """ # 检查设备是否存在 device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) # 获取当前最大版本号 max_version = db.query(func.max(ContentModel.version)).filter( ContentModel.device_id == device_id ).scalar() or 0 # 创建新内容 db_content = ContentModel( device_id=device_id, version=max_version + 1, title=content.title, description=content.description, image_path=content.image_path, layout_config=content.layout_config, timezone=content.timezone, time_format=content.time_format ) db.add(db_content) db.commit() db.refresh(db_content) # 发送MQTT更新通知 mqtt_manager.send_update_command(device_id, db_content.version) return db_content @router.get("/devices/{device_id}/content", response_model=List[ContentSchema]) async def list_content( device_id: str, skip: int = 0, limit: int = 100, is_active: Optional[bool] = None, db: Session = Depends(get_db) ): """ 获取设备内容列表 """ # 检查设备是否存在 device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) query = db.query(ContentModel).filter(ContentModel.device_id == device_id) if is_active is not None: query = query.filter(ContentModel.is_active == is_active) contents = query.order_by(ContentModel.version.desc()).offset(skip).limit(limit).all() return contents @router.get("/devices/{device_id}/content/{version}", response_model=ContentResponse) async def get_content( device_id: str, version: int, db: Session = Depends(get_db) ): """ 获取特定版本的内容详情 """ # 检查设备是否存在 device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) content = db.query(ContentModel).filter( ContentModel.device_id == device_id, ContentModel.version == version ).first() if not content: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="内容不存在" ) # 构建图片URL image_url = None if content.image_path: # 确保路径是相对路径 rel_path = os.path.relpath(content.image_path) image_url = f"/static/{rel_path}" # 解析布局配置 layout_config = None if content.layout_config: try: layout_config = json.loads(content.layout_config) except json.JSONDecodeError: layout_config = None return ContentResponse( version=content.version, title=content.title, description=content.description, image_url=image_url, layout_config=layout_config, timezone=content.timezone, time_format=content.time_format, created_at=content.created_at ) @router.put("/devices/{device_id}/content/{version}", response_model=ContentSchema) async def update_content( device_id: str, version: int, content_update: ContentUpdate, db: Session = Depends(get_db) ): """ 更新内容 """ content = db.query(ContentModel).filter( ContentModel.device_id == device_id, ContentModel.version == version ).first() if not content: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="内容不存在" ) # 更新内容信息 update_data = content_update.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(content, field, value) db.commit() db.refresh(content) # 如果内容被激活,发送MQTT更新通知 if content.is_active: mqtt_manager.send_update_command(device_id, content.version) return content @router.delete("/devices/{device_id}/content/{version}", status_code=status.HTTP_204_NO_CONTENT) async def delete_content( device_id: str, version: int, db: Session = Depends(get_db) ): """ 删除内容 """ content = db.query(ContentModel).filter( ContentModel.device_id == device_id, ContentModel.version == version ).first() if not content: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="内容不存在" ) db.delete(content) db.commit() @router.get("/devices/{device_id}/content/latest", response_model=ContentResponse) async def get_latest_content(device_id: str, db: Session = Depends(get_db)): """ 获取设备的最新活跃内容 """ # 检查设备是否存在 device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) # 获取最新的活跃内容 content = db.query(ContentModel).filter( ContentModel.device_id == device_id, ContentModel.is_active == True ).order_by(ContentModel.version.desc()).first() if not content: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备没有活跃内容" ) # 构建图片URL image_url = None if content.image_path: # 确保路径是相对路径 rel_path = os.path.relpath(content.image_path) image_url = f"/static/{rel_path}" # 解析布局配置 layout_config = None if content.layout_config: try: layout_config = json.loads(content.layout_config) except json.JSONDecodeError: layout_config = None return ContentResponse( version=content.version, title=content.title, description=content.description, image_url=image_url, layout_config=layout_config, timezone=content.timezone, time_format=content.time_format, created_at=content.created_at ) @router.post("/upload") async def upload_image( device_id: str = Query(..., description="设备ID"), version: Optional[int] = Query(None, description="内容版本,如果提供则更新指定版本"), file: UploadFile = File(...), db: Session = Depends(get_db) ): """ 上传图片并处理为墨水屏兼容格式 """ # 检查设备是否存在 device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) # 检查文件类型 if not file.content_type.startswith("image/"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="文件必须是图片格式" ) try: # 保存上传的文件 file_data = await file.read() upload_path = image_processor.save_upload(file_data, file.filename) # 处理图片 processed_path = image_processor.process_image(upload_path) # 如果提供了版本号,更新指定版本 if version: content = db.query(ContentModel).filter( ContentModel.device_id == device_id, ContentModel.version == version ).first() if not content: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="指定版本的内容不存在" ) content.image_path = processed_path db.commit() # 发送MQTT更新通知 mqtt_manager.send_update_command(device_id, version) else: # 创建新内容版本 max_version = db.query(func.max(ContentModel.version)).filter( ContentModel.device_id == device_id ).scalar() or 0 content = ContentModel( device_id=device_id, version=max_version + 1, image_path=processed_path, title=f"图片内容 - {file.filename}", is_active=True ) db.add(content) db.commit() db.refresh(content) # 发送MQTT更新通知 mqtt_manager.send_update_command(device_id, content.version) # 构建图片URL rel_path = os.path.relpath(processed_path) image_url = f"/static/{rel_path}" return { "message": "图片上传并处理成功", "image_url": image_url, "version": content.version if version is None else version } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"图片处理失败: {str(e)}" )