feat: 高德IoT v5 API升级、电子围栏管理、设备绑定自动考勤

- 前向地理编码升级为高德IoT v5 API (POST restapi.amap.com/v5/position/IoT)
- 修复LBS定位偏差: 添加network=LTE参数区分4G/2G, bts格式补充cage字段
- 新增电子围栏管理模块 (circle/polygon/rectangle), 支持地图绘制和POI搜索
- 新增设备-围栏多对多绑定 (DeviceFenceBinding/DeviceFenceState)
- 围栏自动考勤引擎 (fence_checker.py): haversine距离、ray-casting多边形判定、容差机制、防抖
- TCP位置上报自动检测围栏进出, 生成考勤记录并WebSocket广播
- 前端围栏页面: 绑定设备弹窗、POI搜索定位、左侧围栏面板
- 新增fence_attendance WebSocket topic

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
2026-03-27 13:04:11 +00:00
parent cde5146bfe
commit 1d06cc5415
17 changed files with 2303 additions and 187 deletions

View File

@@ -7,9 +7,10 @@ import math
from datetime import datetime
from fastapi import APIRouter, Body, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import require_write
from app.database import get_db
from app.models import LocationRecord
from app.schemas import (
@@ -153,3 +154,22 @@ async def get_location(location_id: int, db: AsyncSession = Depends(get_db)):
if record is None:
raise HTTPException(status_code=404, detail=f"Location {location_id} not found")
return APIResponse(data=LocationRecordResponse.model_validate(record))
@router.delete(
"/{location_id}",
response_model=APIResponse,
summary="删除位置记录 / Delete location record",
dependencies=[Depends(require_write)],
)
async def delete_location(location_id: int, db: AsyncSession = Depends(get_db)):
"""按ID删除位置记录 / Delete location record by ID."""
result = await db.execute(
select(LocationRecord).where(LocationRecord.id == location_id)
)
record = result.scalar_one_or_none()
if record is None:
raise HTTPException(status_code=404, detail=f"Location {location_id} not found")
await db.delete(record)
await db.flush()
return APIResponse(message="Location record deleted")