2026-01-25 22:34:33 +08:00
|
|
|
|
"""基础业务路由(仿真/设备/文件下载等)。
|
|
|
|
|
|
|
|
|
|
|
|
该文件保留项目早期的示例接口与基础能力:
|
|
|
|
|
|
- 健康检查
|
|
|
|
|
|
- 设备连接状态(示例)
|
|
|
|
|
|
- 启停仿真
|
|
|
|
|
|
- 文件下载(带目录穿越保护)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
|
|
from fastapi.responses import FileResponse
|
|
|
|
|
|
|
|
|
|
|
|
from backend.api.schemas import HealthResponse, SimulationStartRequest, SimulationStartResponse, SimulationStopResponse
|
|
|
|
|
|
from backend.services.simulation_manager import SimulationManager
|
|
|
|
|
|
from backend.utils import safe_join
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_router(simulation_manager: SimulationManager, file_root: Path) -> APIRouter:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""构造基础业务路由。
|
|
|
|
|
|
|
|
|
|
|
|
说明:此项目采用“router 工厂函数”风格,通过参数注入 service/配置,而不是全局依赖容器。
|
|
|
|
|
|
"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/health", response_model=HealthResponse)
|
|
|
|
|
|
async def health() -> HealthResponse:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""健康检查(用于容器编排/负载均衡探活)。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
return HealthResponse()
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/devices")
|
|
|
|
|
|
async def devices():
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""返回设备列表(当前为示例数据,反映仿真运行时状态)。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
runtime = simulation_manager.current()
|
|
|
|
|
|
return {
|
|
|
|
|
|
"data": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"device_id": "controlbox_01",
|
|
|
|
|
|
"device_type": "mock_vehicle",
|
|
|
|
|
|
"connected": bool(runtime and runtime.status == "running"),
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/simulation/start", response_model=SimulationStartResponse)
|
|
|
|
|
|
async def start_simulation(body: SimulationStartRequest) -> SimulationStartResponse:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""启动仿真。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
simulation_id = await simulation_manager.start(body.model_dump())
|
|
|
|
|
|
return SimulationStartResponse(simulation_id=simulation_id)
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/simulation/{simulation_id}/stop", response_model=SimulationStopResponse)
|
|
|
|
|
|
async def stop_simulation(simulation_id: str) -> SimulationStopResponse:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""停止仿真。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
await simulation_manager.stop(simulation_id)
|
|
|
|
|
|
return SimulationStopResponse(simulation_id=simulation_id, status="stopped")
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/files/{file_path:path}")
|
|
|
|
|
|
async def files(file_path: str):
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""下载文件(相对 file_root),并校验路径合法性。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
try:
|
|
|
|
|
|
resolved = safe_join(file_root, file_path)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="invalid path")
|
|
|
|
|
|
if not resolved.exists() or not resolved.is_file():
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="not found")
|
|
|
|
|
|
return FileResponse(str(resolved))
|
|
|
|
|
|
|
|
|
|
|
|
return router
|