feat: 性能优化 + 设备总览轨迹展示 + 广播指令API

性能: SQLite WAL模式、aiohttp Session复用、TCP连接锁+空闲超时、
device_id缓存、WebSocket并发广播、API Key认证缓存、围栏N+1查询
批量化、逆地理编码并行化、新增5个DB索引、日志降级DEBUG

功能: 广播指令API(broadcast)、exclude_type低精度后端过滤、
前端设备总览Tab+多设备轨迹叠加+高亮联动+搜索+专属颜色

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

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
2026-03-31 09:41:09 +00:00
parent b970b78136
commit b25eafc483
12 changed files with 1030 additions and 244 deletions

View File

@@ -317,6 +317,81 @@ async def batch_send_command(request: Request, body: BatchCommandRequest, db: As
)
class BroadcastCommandRequest(BaseModel):
"""Request body for broadcasting a command to all devices."""
command_type: str = Field(default="online_cmd", max_length=30, description="指令类型")
command_content: str = Field(..., max_length=500, description="指令内容")
@router.post(
"/broadcast",
response_model=APIResponse[BatchCommandResponse],
status_code=201,
summary="广播指令给所有设备 / Broadcast command to all devices",
dependencies=[Depends(require_write)],
)
@limiter.limit(settings.RATE_LIMIT_WRITE)
async def broadcast_command(request: Request, body: BroadcastCommandRequest, db: AsyncSession = Depends(get_db)):
"""
向所有设备广播同一指令。尝试通过 TCP 发送给每台设备TCP 未连接的自动跳过。
Broadcast the same command to all devices. Attempts TCP send for each, skips those without active TCP connection.
"""
from sqlalchemy import select
from app.models import Device
result = await db.execute(select(Device))
devices = list(result.scalars().all())
if not devices:
return APIResponse(
message="No devices / 没有设备",
data=BatchCommandResponse(total=0, sent=0, failed=0, results=[]),
)
results = []
for device in devices:
if not tcp_command_service.is_device_online(device.imei):
results.append(BatchCommandResult(
device_id=device.id, imei=device.imei,
success=False, error="TCP not connected",
))
continue
try:
cmd_log = await command_service.create_command(
db,
device_id=device.id,
command_type=body.command_type,
command_content=body.command_content,
)
await tcp_command_service.send_command(
device.imei, body.command_type, body.command_content
)
cmd_log.status = "sent"
cmd_log.sent_at = now_cst()
await db.flush()
await db.refresh(cmd_log)
results.append(BatchCommandResult(
device_id=device.id, imei=device.imei,
success=True, command_id=cmd_log.id,
))
except Exception as e:
logging.getLogger(__name__).error("Broadcast cmd failed for %s: %s", device.imei, e)
results.append(BatchCommandResult(
device_id=device.id, imei=device.imei,
success=False, error="Send failed",
))
sent = sum(1 for r in results if r.success)
failed = len(results) - sent
return APIResponse(
message=f"Broadcast: {sent} sent, {failed} skipped (total: {len(devices)})",
data=BatchCommandResponse(
total=len(results), sent=sent, failed=failed, results=results,
),
)
@router.get(
"/{command_id}",
response_model=APIResponse[CommandResponse],