""" Devices Router - 设备管理接口 API endpoints for device CRUD operations and statistics. """ import math from fastapi import APIRouter, Depends, HTTPException, Query, Request from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.schemas import ( APIResponse, BatchDeviceCreateRequest, BatchDeviceCreateResponse, BatchDeviceCreateResult, BatchDeviceDeleteRequest, BatchDeviceUpdateRequest, DeviceCreate, DeviceResponse, DeviceUpdate, PaginatedList, ) from app.config import settings from app.dependencies import require_write from app.extensions import limiter from app.schemas import LocationRecordResponse from app.services import device_service, location_service router = APIRouter(prefix="/api/devices", tags=["Devices / 设备管理"]) @router.get( "", response_model=APIResponse[PaginatedList[DeviceResponse]], summary="获取设备列表 / List devices", ) async def list_devices( page: int = Query(default=1, ge=1, description="页码 / Page number"), page_size: int = Query(default=20, ge=1, le=100, description="每页数量 / Items per page"), status: str | None = Query(default=None, description="状态过滤 / Status filter (online/offline)"), search: str | None = Query(default=None, description="搜索IMEI或名称 / Search by IMEI or name"), db: AsyncSession = Depends(get_db), ): """ 获取设备列表,支持分页、状态过滤和搜索。 List devices with pagination, optional status filter, and search. """ devices, total = await device_service.get_devices( db, page=page, page_size=page_size, status_filter=status, search=search ) return APIResponse( data=PaginatedList( items=[DeviceResponse.model_validate(d) for d in devices], total=total, page=page, page_size=page_size, total_pages=math.ceil(total / page_size) if total else 0, ) ) @router.get( "/stats", response_model=APIResponse[dict], summary="获取设备统计 / Get device statistics", ) async def device_stats(db: AsyncSession = Depends(get_db)): """ 获取设备统计信息:总数、在线、离线。 Get device statistics: total, online, offline counts. """ stats = await device_service.get_device_stats(db) return APIResponse(data=stats) @router.get( "/imei/{imei}", response_model=APIResponse[DeviceResponse], summary="按IMEI查询设备 / Get device by IMEI", ) async def get_device_by_imei(imei: str, db: AsyncSession = Depends(get_db)): """ 按IMEI号查询设备信息。 Get device details by IMEI number. """ device = await device_service.get_device_by_imei(db, imei) if device is None: raise HTTPException(status_code=404, detail=f"Device with IMEI {imei} not found / 未找到IMEI为{imei}的设备") return APIResponse(data=DeviceResponse.model_validate(device)) @router.get( "/all-latest-locations", response_model=APIResponse[list[LocationRecordResponse]], summary="获取所有在线设备位置 / Get all online device locations", ) async def all_latest_locations(db: AsyncSession = Depends(get_db)): """ 获取所有在线设备的最新位置,用于地图总览。 Get latest location for all online devices, for map overview. """ records = await location_service.get_all_online_latest_locations(db) return APIResponse(data=[LocationRecordResponse.model_validate(r) for r in records]) @router.post( "/batch", response_model=APIResponse[BatchDeviceCreateResponse], status_code=201, summary="批量创建设备 / Batch create devices", dependencies=[Depends(require_write)], ) @limiter.limit(settings.RATE_LIMIT_WRITE) async def batch_create_devices(request: Request, body: BatchDeviceCreateRequest, db: AsyncSession = Depends(get_db)): """ 批量注册设备(最多500台),跳过IMEI重复的设备。 Batch register devices (up to 500). Skips devices with duplicate IMEIs. """ results = await device_service.batch_create_devices(db, body.devices) created = sum(1 for r in results if r["success"]) failed = len(results) - created return APIResponse( message=f"Batch create: {created} created, {failed} failed", data=BatchDeviceCreateResponse( total=len(results), created=created, failed=failed, results=[BatchDeviceCreateResult(**r) for r in results], ), ) @router.put( "/batch", response_model=APIResponse[dict], summary="批量更新设备 / Batch update devices", dependencies=[Depends(require_write)], ) @limiter.limit(settings.RATE_LIMIT_WRITE) async def batch_update_devices(request: Request, body: BatchDeviceUpdateRequest, db: AsyncSession = Depends(get_db)): """ 批量更新设备信息(名称、状态等),最多500台。 Batch update device fields (name, status, etc.) for up to 500 devices. """ results = await device_service.batch_update_devices(db, body.device_ids, body.update) updated = sum(1 for r in results if r["success"]) failed = len(results) - updated return APIResponse( message=f"Batch update: {updated} updated, {failed} failed", data={"total": len(results), "updated": updated, "failed": failed, "results": results}, ) @router.post( "/batch-delete", response_model=APIResponse[dict], summary="批量删除设备 / Batch delete devices", dependencies=[Depends(require_write)], ) @limiter.limit(settings.RATE_LIMIT_WRITE) async def batch_delete_devices( request: Request, body: BatchDeviceDeleteRequest, db: AsyncSession = Depends(get_db), ): """ 批量删除设备(最多100台)。通过 POST body 传递 device_ids 列表。 Batch delete devices (up to 100). Pass device_ids in request body. """ results = await device_service.batch_delete_devices(db, body.device_ids) deleted = sum(1 for r in results if r["success"]) failed = len(results) - deleted return APIResponse( message=f"Batch delete: {deleted} deleted, {failed} failed", data={"total": len(results), "deleted": deleted, "failed": failed, "results": results}, ) @router.get( "/{device_id}", response_model=APIResponse[DeviceResponse], summary="获取设备详情 / Get device details", ) async def get_device(device_id: int, db: AsyncSession = Depends(get_db)): """ 按ID获取设备详细信息。 Get device details by ID. """ device = await device_service.get_device(db, device_id) if device is None: raise HTTPException(status_code=404, detail=f"Device {device_id} not found / 未找到设备{device_id}") return APIResponse(data=DeviceResponse.model_validate(device)) @router.post( "", response_model=APIResponse[DeviceResponse], status_code=201, summary="创建设备 / Create device", dependencies=[Depends(require_write)], ) async def create_device(device_data: DeviceCreate, db: AsyncSession = Depends(get_db)): """ 手动注册新设备。 Manually register a new device. """ # Check for duplicate IMEI existing = await device_service.get_device_by_imei(db, device_data.imei) if existing is not None: raise HTTPException( status_code=400, detail=f"Device with IMEI {device_data.imei} already exists / IMEI {device_data.imei} 已存在", ) device = await device_service.create_device(db, device_data) return APIResponse(data=DeviceResponse.model_validate(device)) @router.put( "/{device_id}", response_model=APIResponse[DeviceResponse], summary="更新设备信息 / Update device", dependencies=[Depends(require_write)], ) async def update_device( device_id: int, device_data: DeviceUpdate, db: AsyncSession = Depends(get_db) ): """ 更新设备信息(名称、状态等)。 Update device information (name, status, etc.). """ device = await device_service.update_device(db, device_id, device_data) if device is None: raise HTTPException(status_code=404, detail=f"Device {device_id} not found / 未找到设备{device_id}") return APIResponse(data=DeviceResponse.model_validate(device)) @router.delete( "/{device_id}", response_model=APIResponse, summary="删除设备 / Delete device", dependencies=[Depends(require_write)], ) async def delete_device(device_id: int, db: AsyncSession = Depends(get_db)): """ 删除设备及其关联数据。 Delete a device and all associated records. """ deleted = await device_service.delete_device(db, device_id) if not deleted: raise HTTPException(status_code=404, detail=f"Device {device_id} not found / 未找到设备{device_id}") return APIResponse(message="Device deleted successfully / 设备删除成功")