101 lines
3.4 KiB
Python
101 lines
3.4 KiB
Python
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from fastapi import FastAPI
|
||
|
|
from fastapi.responses import HTMLResponse
|
||
|
|
from fastapi.staticfiles import StaticFiles
|
||
|
|
from contextlib import asynccontextmanager
|
||
|
|
from app.database import init_db, async_session
|
||
|
|
from app.tcp_server import tcp_manager
|
||
|
|
from app.config import settings
|
||
|
|
from app.routers import devices, locations, alarms, attendance, commands, bluetooth, beacons
|
||
|
|
import asyncio
|
||
|
|
import logging
|
||
|
|
|
||
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
@asynccontextmanager
|
||
|
|
async def lifespan(app: FastAPI):
|
||
|
|
# Startup
|
||
|
|
logger.info("Initializing database...")
|
||
|
|
await init_db()
|
||
|
|
# Reset all devices to offline on startup (stale state from previous run)
|
||
|
|
try:
|
||
|
|
from sqlalchemy import update
|
||
|
|
from app.models import Device
|
||
|
|
async with async_session() as session:
|
||
|
|
async with session.begin():
|
||
|
|
await session.execute(update(Device).values(status="offline"))
|
||
|
|
logger.info("All devices reset to offline on startup")
|
||
|
|
except Exception:
|
||
|
|
logger.exception("Failed to reset device statuses on startup")
|
||
|
|
logger.info("Starting TCP server on %s:%d", settings.TCP_HOST, settings.TCP_PORT)
|
||
|
|
tcp_task = asyncio.create_task(tcp_manager.start(settings.TCP_HOST, settings.TCP_PORT))
|
||
|
|
yield
|
||
|
|
# Shutdown
|
||
|
|
logger.info("Shutting down TCP server...")
|
||
|
|
await tcp_manager.stop()
|
||
|
|
tcp_task.cancel()
|
||
|
|
|
||
|
|
app = FastAPI(
|
||
|
|
title="KKS Badge Management System / KKS工牌管理系统",
|
||
|
|
description="""
|
||
|
|
## KKS P240 & P241 蓝牙工牌管理后台
|
||
|
|
|
||
|
|
### 功能模块 / Features:
|
||
|
|
- **设备管理 / Device Management** - 设备注册、状态监控
|
||
|
|
- **位置数据 / Location Data** - GPS/LBS/WIFI定位数据查询与轨迹回放
|
||
|
|
- **报警管理 / Alarm Management** - SOS、围栏、低电等报警处理
|
||
|
|
- **考勤管理 / Attendance** - 打卡记录查询与统计
|
||
|
|
- **指令管理 / Commands** - 远程指令下发与留言
|
||
|
|
- **蓝牙数据 / Bluetooth** - 蓝牙打卡与定位数据
|
||
|
|
- **信标管理 / Beacons** - 蓝牙信标注册与位置配置
|
||
|
|
|
||
|
|
### 通讯协议 / Protocol:
|
||
|
|
- TCP端口: {tcp_port} (设备连接)
|
||
|
|
- 支持协议: KKS P240/P241 通讯协议
|
||
|
|
""".format(tcp_port=settings.TCP_PORT),
|
||
|
|
version="1.0.0",
|
||
|
|
docs_url="/docs",
|
||
|
|
redoc_url="/redoc",
|
||
|
|
lifespan=lifespan,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Include routers
|
||
|
|
app.include_router(devices.router)
|
||
|
|
app.include_router(locations.router)
|
||
|
|
app.include_router(alarms.router)
|
||
|
|
app.include_router(attendance.router)
|
||
|
|
app.include_router(commands.router)
|
||
|
|
app.include_router(bluetooth.router)
|
||
|
|
app.include_router(beacons.router)
|
||
|
|
|
||
|
|
_STATIC_DIR = Path(__file__).parent / "static"
|
||
|
|
app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static")
|
||
|
|
|
||
|
|
|
||
|
|
@app.get("/admin", response_class=HTMLResponse, tags=["Admin"])
|
||
|
|
async def admin_page():
|
||
|
|
"""管理后台页面 / Admin Dashboard"""
|
||
|
|
html_path = _STATIC_DIR / "admin.html"
|
||
|
|
return HTMLResponse(content=html_path.read_text(encoding="utf-8"))
|
||
|
|
|
||
|
|
|
||
|
|
@app.get("/", tags=["Root"])
|
||
|
|
async def root():
|
||
|
|
return {
|
||
|
|
"name": settings.APP_NAME,
|
||
|
|
"version": "1.0.0",
|
||
|
|
"docs": "/docs",
|
||
|
|
"redoc": "/redoc",
|
||
|
|
"admin": "/admin",
|
||
|
|
"tcp_port": settings.TCP_PORT,
|
||
|
|
}
|
||
|
|
|
||
|
|
@app.get("/health", tags=["Root"])
|
||
|
|
async def health():
|
||
|
|
return {
|
||
|
|
"status": "healthy",
|
||
|
|
"connected_devices": len(tcp_manager.connections),
|
||
|
|
}
|