feat: 信标设备绑定 + 蓝牙模式管理 + 系统管理增强 + 数据导出

- 新增 DeviceBeaconBinding 模型,信标-设备多对多绑定 CRUD
- 蓝牙打卡模式批量配置/恢复正常模式 API
- 反向同步: 查询设备 BTMACSET 配置更新数据库绑定 (独立 session 解决事务隔离)
- 设备列表快捷操作弹窗修复 (fire-and-forget IIFE 替代阻塞轮询)
- 保存按钮防抖: 围栏/信标绑定保存点击后 disabled + 转圈防重复提交
- 审计日志中间件 + 系统配置/备份/固件 API
- 设备分组管理 + 告警规则配置
- 5个数据导出 API (CSV UTF-8 BOM)
- 位置热力图 + 告警条件删除 + 位置清理

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

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
2026-04-01 07:06:37 +00:00
parent 9daa81621c
commit 9cd9dd9d76
19 changed files with 3403 additions and 100 deletions

109
app/routers/alert_rules.py Normal file
View File

@@ -0,0 +1,109 @@
"""
Alert Rules Router - 告警规则配置接口
API endpoints for alert rule CRUD operations.
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.dependencies import require_write
from app.models import AlertRule
from app.schemas import (
APIResponse,
AlertRuleCreate,
AlertRuleResponse,
AlertRuleUpdate,
)
router = APIRouter(prefix="/api/alert-rules", tags=["Alert Rules / 告警规则"])
@router.get(
"",
response_model=APIResponse[list[AlertRuleResponse]],
summary="获取告警规则列表 / List alert rules",
)
async def list_rules(db: AsyncSession = Depends(get_db)):
"""获取所有告警规则。"""
result = await db.execute(select(AlertRule).order_by(AlertRule.id))
rules = list(result.scalars().all())
return APIResponse(data=[AlertRuleResponse.model_validate(r) for r in rules])
@router.post(
"",
response_model=APIResponse[AlertRuleResponse],
status_code=201,
summary="创建告警规则 / Create alert rule",
dependencies=[Depends(require_write)],
)
async def create_rule(body: AlertRuleCreate, db: AsyncSession = Depends(get_db)):
"""创建新告警规则。"""
rule = AlertRule(
name=body.name,
rule_type=body.rule_type,
conditions=body.conditions,
is_active=body.is_active,
device_ids=body.device_ids,
group_id=body.group_id,
description=body.description,
)
db.add(rule)
await db.flush()
await db.refresh(rule)
return APIResponse(data=AlertRuleResponse.model_validate(rule))
@router.get(
"/{rule_id}",
response_model=APIResponse[AlertRuleResponse],
summary="获取告警规则详情 / Get alert rule",
)
async def get_rule(rule_id: int, db: AsyncSession = Depends(get_db)):
"""获取告警规则详情。"""
result = await db.execute(select(AlertRule).where(AlertRule.id == rule_id))
rule = result.scalar_one_or_none()
if not rule:
raise HTTPException(status_code=404, detail=f"规则 {rule_id} 不存在")
return APIResponse(data=AlertRuleResponse.model_validate(rule))
@router.put(
"/{rule_id}",
response_model=APIResponse[AlertRuleResponse],
summary="更新告警规则 / Update alert rule",
dependencies=[Depends(require_write)],
)
async def update_rule(
rule_id: int, body: AlertRuleUpdate, db: AsyncSession = Depends(get_db)
):
"""更新告警规则。"""
result = await db.execute(select(AlertRule).where(AlertRule.id == rule_id))
rule = result.scalar_one_or_none()
if not rule:
raise HTTPException(status_code=404, detail=f"规则 {rule_id} 不存在")
update_data = body.model_dump(exclude_unset=True)
for k, v in update_data.items():
setattr(rule, k, v)
await db.flush()
await db.refresh(rule)
return APIResponse(data=AlertRuleResponse.model_validate(rule))
@router.delete(
"/{rule_id}",
response_model=APIResponse,
summary="删除告警规则 / Delete alert rule",
dependencies=[Depends(require_write)],
)
async def delete_rule(rule_id: int, db: AsyncSession = Depends(get_db)):
"""删除告警规则。"""
result = await db.execute(select(AlertRule).where(AlertRule.id == rule_id))
rule = result.scalar_one_or_none()
if not rule:
raise HTTPException(status_code=404, detail=f"规则 {rule_id} 不存在")
await db.delete(rule)
await db.flush()
return APIResponse(message="规则已删除")