Add batch management APIs, API security, rate limiting, and optimizations

- Batch device CRUD: POST /api/devices/batch (create 500), PUT /api/devices/batch (update 500),
  POST /api/devices/batch-delete (delete 100) with WHERE IN bulk queries
- Batch command: POST /api/commands/batch with model_validator mutual exclusion
- API key auth (X-API-Key header, secrets.compare_digest timing-safe)
- Rate limiting via SlowAPIMiddleware (60/min default, 30/min writes)
- Real client IP extraction (X-Forwarded-For / CF-Connecting-IP)
- Global exception handler (no stack trace leaks, passes HTTPException through)
- CORS with auto-disable credentials on wildcard origins
- Schema validation: IMEI pattern, lat/lon ranges, Literal enums, MAC/UUID patterns
- Heartbeats router, per-ID endpoints for locations/attendance/bluetooth
- Input dedup in batch create, result ordering preserved
- Baidu reverse geocoding, Gaode map tiles with WGS84→GCJ02 conversion
- Device detail panel with feature toggles and command controls
- Side panel for location/beacon pages with auto-select active device

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

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
2026-03-20 09:18:43 +00:00
parent 1bdbe4fa19
commit 7d6040af41
23 changed files with 1564 additions and 294 deletions

100
CLAUDE.md
View File

@@ -10,6 +10,7 @@ KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio T
```
/home/gpsystem/
├── run.py # 启动脚本 (uvicorn)
├── .env.example # 环境变量模板 (复制为 .env 使用)
├── requirements.txt # Python 依赖
├── frpc.toml # FRP 客户端配置 (TCP隧道)
├── badge_admin.db # SQLite 数据库
@@ -17,8 +18,10 @@ KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio T
├── app/
│ ├── main.py # FastAPI 应用入口, 挂载静态文件, 启动TCP服务器
│ ├── config.py # 配置 (端口8088 HTTP, 5000 TCP)
│ ├── config.py # 配置 (pydantic-settings, .env支持, 端口/API密钥/缓存/限流)
│ ├── database.py # SQLAlchemy async 数据库连接
│ ├── dependencies.py # FastAPI 依赖 (API Key 认证)
│ ├── extensions.py # 共享实例 (rate limiter, 真实IP提取)
│ ├── models.py # ORM 模型 (Device, LocationRecord, AlarmRecord, HeartbeatRecord, AttendanceRecord, BluetoothRecord, BeaconConfig, CommandLog)
│ ├── schemas.py # Pydantic 请求/响应模型
│ ├── tcp_server.py # TCP 服务器核心 (~2400行), 管理设备连接、协议处理、数据持久化
@@ -31,18 +34,20 @@ KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio T
│ │ └── crc.py # CRC-ITU (CRC-16/X-25, 多项式 0x8408)
│ │
│ ├── services/
│ │ ├── device_service.py # 设备 CRUD
│ │ ├── command_service.py # 指令日志 CRUD
│ │ ├── location_service.py # 位置记录查询
│ │ ── beacon_service.py # 蓝牙信标 CRUD
│ │ ├── device_service.py # 设备 CRUD
│ │ ├── command_service.py # 指令日志 CRUD
│ │ ├── location_service.py # 位置记录查询
│ │ ── beacon_service.py # 蓝牙信标 CRUD
│ │ └── tcp_command_service.py # TCP指令抽象层 (解耦routers↔tcp_server)
│ │
│ ├── routers/
│ │ ├── devices.py # /api/devices (含 /stats 统计接口)
│ │ ├── commands.py # /api/commands (含 /send, /message, /tts)
│ │ ├── locations.py # /api/locations (含 /latest, /track)
│ │ ├── devices.py # /api/devices (含 /stats, /batch, /batch-delete)
│ │ ├── commands.py # /api/commands (含 /send, /message, /tts, /batch)
│ │ ├── locations.py # /api/locations (含 /latest, /track, /{id})
│ │ ├── alarms.py # /api/alarms (含 acknowledge)
│ │ ├── attendance.py # /api/attendance
│ │ ├── bluetooth.py # /api/bluetooth
│ │ ├── attendance.py # /api/attendance (含 /stats, /{id})
│ │ ├── bluetooth.py # /api/bluetooth (含 /{id})
│ │ ├── heartbeats.py # /api/heartbeats (心跳记录查询)
│ │ └── beacons.py # /api/beacons (信标管理 CRUD)
│ │
│ └── static/
@@ -75,11 +80,18 @@ KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio T
- **7000**: FRP 服务端管理端口
- **7500**: FRP Dashboard (admin/PassWord0325)
### 配置管理
- `app/config.py` 使用 **pydantic-settings** (`BaseSettings`),支持 `.env` 文件覆盖默认值
- `.env.example` 提供所有可配置项模板,复制为 `.env` 使用
- DATABASE_URL 使用绝对路径 (基于 `__file__` 计算项目根目录)
- 所有 API 密钥 (天地图/Google/Unwired/高德) 集中在 `config.py``geocoding.py``settings` 导入
- 端口号有 pydantic 校验 (ge=1, le=65535)
## 启动服务
```bash
# 启动服务
cd /tmp/badge-admin
cd /home/gpsystem
nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8088 > server.log 2>&1 &
# 启动 FRP 客户端 (TCP隧道)
@@ -192,6 +204,19 @@ KKS 二进制协议,详见 `doc/KKS_Protocol_P240_P241.md`
- **配额**: 免费 10,000 次/天
- **注意**: postStr 参数需使用双引号JSON并URL编码
### API 认证与限流
- **认证**: 设置 `API_KEY` 环境变量后,所有 `/api/` 请求需携带 `X-API-Key` 请求头
- **限流**: 全局 60/min (default_limits),写操作 30/min (`@limiter.limit`)
- **真实 IP**: 从 `X-Forwarded-For``CF-Connecting-IP``request.client.host` 提取
- **CORS**: `CORS_ORIGINS=*` 时自动禁用 `allow_credentials`
### 批量操作 API
- `POST /api/devices/batch` — 批量创建 (最多500),输入去重 + DB去重结果按输入顺序
- `PUT /api/devices/batch` — 批量更新,单次 WHERE IN 查询 + 单次 flush
- `POST /api/devices/batch-delete` — 批量删除 (最多100),通过 body 传 device_ids
- `POST /api/commands/batch` — 批量发送指令 (最多100)`model_validator` 互斥校验
- 所有批量操作使用 WHERE IN 批量查询,避免 N+1
### API 分页
- page_size 最大限制为 100 (schema 层校验)
- 前端设备选择器使用 page_size=100 (不能超过限制)
@@ -209,6 +234,7 @@ KKS 二进制协议,详见 `doc/KKS_Protocol_P240_P241.md`
- TTS: 通过 0x80 发送 `TTS,<文本>` 格式
- 常用指令: `GPSON#` 开启GPS, `BTON#` 开启蓝牙, `BTSCAN,1#` 开启BLE扫描
- 已验证可用指令: `BTON#`, `BTSCAN,1#`, `GPSON#`, `MODE,1/3#`, `PARAM#`, `CHECK#`, `VERSION#`, `TIMER#`, `WHERE#`, `STATUS#`, `RESET#`
- **架构**: `tcp_command_service.py` 作为抽象层解耦 routers↔tcp_server 的循环导入,通过 lazy import 访问 tcp_manager
- **重要**: TCP 层 (`send_command`/`send_message`) 只负责发送,不创建 CommandLog。CommandLog 由 API 层 (commands.py) 管理
- **重要**: 服务器启动时自动将所有设备标记为 offline等待设备重新登录
@@ -219,7 +245,9 @@ KKS 二进制协议,详见 `doc/KKS_Protocol_P240_P241.md`
- **多信标定位**: 0xB3 多信标场景,取 RSSI 信号最强的已注册信标位置
- **设备端配置**: 需通过 0x80 指令发送 `BTON#` 开启蓝牙、`BTSCAN,1#` 开启扫描
- **已知有效指令**: `BTON#`(btON:1), `BTSCAN,1#`(btSCAN:1) — 设备确认设置成功
- **当前状态**: 设备接受BT指令但尚未上报0xB2/0xB3数据可能需要信标UUID匹配或通过Tracksolid平台配置
- **当前状态**: ✅ 0xB2 蓝牙打卡已验证可用 (2026-03-18),设备成功检测信标并上报数据
- **已验证信标**: MAC=`C3:00:00:34:43:5E`, UUID=FDA50693-A4E2-4FB1-AFCF-C6EB07647825, Major=10001, Minor=19641
- **注意**: 信标需配置经纬度/地址,否则打卡记录中位置为空
- **制造商**: 几米物联 (Jimi IoT / jimiiot.com.cn)P240/P241 智能电子工牌系列
### 0x94 General Info 子协议
@@ -289,6 +317,18 @@ remotePort = 5001
3. 注意返回坐标为 GCJ-02需转换为 WGS-84 用于 Leaflet 地图
4. 高德数字签名: 参数按key排序拼接 + 安全密钥 → MD5 → sig 参数
## 百度地图 API
### Key
- **服务端 AK**: `nZ4AyCm7FTn85HbFuQjw0ItSYkgxEuhA`
### 已接入服务
- **✅ 逆地理编码**: `api.map.baidu.com/reverse_geocoding/v3` — 经纬度 → 地址 (coordtype=wgs84ll, 无需坐标转换)
- 优先级: 百度 > 天地图 (fallback)
- 配额: 5,000次/日 (个人开发者)
- **注意**: 百度内部使用 BD-09 坐标系,但逆地理编码接口支持 `coordtype=wgs84ll` 直接传入 WGS-84 坐标
- 百度**无服务端基站/WiFi定位API**,基站定位仍用 Mylnikov
## 已知限制
1. **IoT SIM 卡不支持 SMS** - 144 号段的物联网卡无法收发短信,需通过平台或 TCP 连接配置设备
@@ -296,7 +336,7 @@ remotePort = 5001
3. **SQLite 单写** - 高并发场景需切换 PostgreSQL
4. **设备最多 100 台列表** - 受 page_size 限制,超过需翻页查询
5. **基站定位精度差** - 当前 Mylnikov API 中国基站精度 ~16km待接入高德智能硬件定位后可达 ~30m
6. **天地图逆地理编码使用 HTTP** - API不支持HTTPSKey在URL中明文传输 (低风险: 免费Key)
6. **天地图逆地理编码使用 HTTP** - API不支持HTTPSKey在URL中明文传输 (低风险: 免费Key, 已降级为备选)
## 已修复的问题 (Bug Fix 记录)
@@ -361,6 +401,36 @@ remotePort = 5001
42. **前端4G筛选** - 位置类型筛选添加 gps_4g/wifi_4g/lbs_4g 选项
43. **DATA_REPORT_MODES** - 修正所有模式名称匹配协议文档
### 架构优化 (2026-03-17)
44. **配置集中化** - API密钥从 geocoding.py 硬编码移至 config.py (pydantic-settings),支持 .env 覆盖
45. **数据库绝对路径** - DATABASE_URL 从相对路径改为基于 `__file__` 的绝对路径,避免 CWD 依赖
46. **tcp_command_service 抽象层** - 新建 services/tcp_command_service.py通过 lazy import 解耦 routers↔tcp_server 循环依赖
47. **commands.py 去重** - send_command/send_message/send_tts 提取 `_send_to_device()` 通用函数
48. **协议常量扩展** - constants.py 新增 DEFAULT_DEVICE_TYPE, SERVER_FLAG_BYTES, ATTENDANCE_TYPES, COURSE_BIT_*, MCC_MNC2_FLAG, VOLTAGE_LEVELS
49. **前端侧边面板** - 位置追踪/信标管理页面添加左侧设备/信标列表面板,自动选中最近活跃设备
50. **前端面板解耦** - 提取 PANEL_IDS 配置 + _initPanelRender 通用函数toggleSidePanel 仅在 locations 页调用 invalidateSize
### 百度地图接入 & 连接修复 (2026-03-19)
51. **百度逆地理编码** - 接入百度地图 reverse_geocoding/v3 API (coordtype=wgs84ll),优先于天地图
52. **PROTO_ADDRESS_REPLY_EN 未导入** - 0xA5 报警地址回复时 NameError补充 import
53. **心跳自动恢复 online** - 心跳处理时自动将设备 status 设为 online + 重新注册 connections
54. **高德地图瓦片** - 替换天地图瓦片为高德 (GCJ-02, 标准Mercator),添加 WGS84→GCJ02 坐标转换,可切换 provider (`MAP_PROVIDER` 变量)
### API 安全加固 & 批量管理 (2026-03-20)
55. **API Key 认证** - `dependencies.py` 实现 X-API-Key 头认证,`secrets.compare_digest` 防时序攻击
56. **CORS + 限流** - `SlowAPIMiddleware` 全局限流 (60/min),写操作独立限速 (30/min)
57. **限流器真实 IP** - `extensions.py``X-Forwarded-For` / `CF-Connecting-IP` 提取真实客户端 IP
58. **全局异常处理** - 拦截未处理异常返回 500不泄露堆栈放行 HTTPException/ValidationError
59. **Schema 校验加强** - IMEI pattern、经纬度范围、Literal 枚举、command max_length、BeaconConfig MAC/UUID pattern
60. **Health 端点增强** - `/health` 检测数据库连通性 + TCP 连接设备数
61. **批量设备创建** - `POST /api/devices/batch` (最多500台)WHERE IN 单次查询去重,输入列表内 IMEI 去重
62. **批量设备更新** - `PUT /api/devices/batch`,单次查询 + 批量更新 + 单次 flush
63. **批量设备删除** - `POST /api/devices/batch-delete`,通过 body 传递避免 URL 长度限制
64. **批量指令发送** - `POST /api/commands/batch` (最多100台)`model_validator` 互斥校验 device_ids/imeis
65. **Heartbeats 路由** - 新增 `GET /api/heartbeats` 心跳记录查询 + 按 ID 获取
66. **按 ID 查询端点** - locations/{id}, attendance/{id}, bluetooth/{id} 放在路由末尾避免冲突
67. **Beacons double-commit 修复** - 移除 router 层多余的 flush/refresh依赖 service 层
### 0x94 子协议 0x04
- 设备配置上报: `ALM2=40;ALM4=E0;MODE=03;IMSI=460240388355286`
- 在设备重连/重启后上报
@@ -368,9 +438,9 @@ remotePort = 5001
## 待完成功能
1. **⭐ 接入高德智能硬件定位** - 企业认证通过后,替换 Mylnikov大幅提升 WiFi/基站定位精度
2. **地图瓦片底图** - 使用浏览器端 Key 替换 OpenStreetMap 瓦片 (中国地区显示更准确)
2. ~~**地图瓦片**~~ - ✅ 已切换为高德瓦片 (GCJ-02),支持 MAP_PROVIDER 切换 ('gaode'|'tianditu')
3. **心跳扩展模块解析** - 计步器、外部电压等模块未解析
4. **蓝牙信标调试** - P241 接受 BTON/BTSCAN 但未上报BLE数据需确认信标iBeacon广播格式及UUID匹配
4. ~~**蓝牙信标调试**~~ - ✅ 已完成 (2026-03-18)0xB2打卡数据正常上报信标匹配成功
## 调试技巧