Files
desungongpai/CLAUDE.md
default d54e53e0b7 feat: KKS P240/P241 蓝牙工牌管理系统初始提交
FastAPI + SQLAlchemy + asyncio TCP 服务器,支持设备管理、实时定位、
告警、考勤打卡、蓝牙记录、指令下发、TTS语音播报等功能。
2026-03-27 10:19:34 +00:00

560 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Badge Admin - KKS P240/P241 蓝牙工牌管理系统
## 项目概览
KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio TCP 服务器。
支持设备管理、实时定位、告警、考勤打卡、蓝牙记录、指令下发、TTS语音播报等功能。
## 项目结构
```
/home/gpsystem/
├── run.py # 启动脚本 (uvicorn)
├── .env.example # 环境变量模板 (复制为 .env 使用)
├── requirements.txt # Python 依赖
├── frpc.toml # FRP 客户端配置 (TCP隧道)
├── badge_admin.db # SQLite 数据库
├── server.log # 服务器日志
├── app/
│ ├── main.py # FastAPI 应用入口, 挂载静态文件, 启动TCP服务器
│ ├── config.py # 配置 (pydantic-settings, .env支持, 端口/API密钥/缓存/限流)
│ ├── database.py # SQLAlchemy async 数据库连接
│ ├── dependencies.py # FastAPI 依赖 (多API Key认证 + 权限控制: read/write/admin)
│ ├── extensions.py # 共享实例 (rate limiter, 真实IP提取)
│ ├── websocket_manager.py # WebSocket 连接管理器 (topic订阅, 实时广播)
│ ├── models.py # ORM 模型 (Device, LocationRecord, AlarmRecord, HeartbeatRecord, AttendanceRecord, BluetoothRecord, BeaconConfig, CommandLog, ApiKey)
│ ├── schemas.py # Pydantic 请求/响应模型
│ ├── tcp_server.py # TCP 服务器核心 (~2400行), 管理设备连接、协议处理、数据持久化
│ ├── geocoding.py # 高德地理编码服务 (基站/WiFi → 经纬度 + 经纬度 → 地址)
│ │
│ ├── protocol/
│ │ ├── constants.py # 协议常量 (协议号、告警类型snake_case、信号等级等)
│ │ ├── parser.py # 二进制协议解析器
│ │ ├── builder.py # 响应包构建器
│ │ └── 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
│ │ └── tcp_command_service.py # TCP指令抽象层 (解耦routers↔tcp_server)
│ │
│ ├── routers/
│ │ ├── devices.py # /api/devices (含 /stats, /batch, /batch-delete, /all-latest-locations)
│ │ ├── commands.py # /api/commands (含 /send, /message, /tts, /batch)
│ │ ├── locations.py # /api/locations (含 /latest, /batch-latest, /track, /{id})
│ │ ├── alarms.py # /api/alarms (含 acknowledge, alarm_source过滤)
│ │ ├── attendance.py # /api/attendance (含 /stats, /{id})
│ │ ├── bluetooth.py # /api/bluetooth (含 beacon_mac过滤, /{id})
│ │ ├── heartbeats.py # /api/heartbeats (心跳记录查询)
│ │ ├── beacons.py # /api/beacons (信标管理 CRUD)
│ │ ├── api_keys.py # /api/keys (API密钥管理 CRUD, admin only)
│ │ └── ws.py # /ws (WebSocket实时推送, topic订阅)
│ │
│ └── static/
│ └── admin.html # 管理后台 SPA (暗色主题, 8个页面)
└── doc/
└── KKS_Protocol_P240_P241.md # 协议文档 (从PDF转换)
```
## 架构设计
### 网络拓扑
```
[P241 工牌] --TCP--> [152.69.205.186:5001 (frps)]
|
FRP Tunnel
|
[Container:5000 (frpc)]
|
[TCP Server (asyncio)]
|
[FastAPI :8088] --CF Tunnel--> [Web 访问]
```
### 端口说明
- **8088**: FastAPI HTTP API + 管理后台 UI
- **5000**: TCP 服务器 (KKS 二进制协议)
- **5001**: 远程服务器 FRP 映射端口 (152.69.205.186)
- **7000**: FRP 服务端管理端口
- **7500**: FRP Dashboard (admin/PassWord0325)
### 配置管理
- `app/config.py` 使用 **pydantic-settings** (`BaseSettings`),支持 `.env` 文件覆盖默认值
- `.env.example` 提供所有可配置项模板,复制为 `.env` 使用
- DATABASE_URL 使用绝对路径 (基于 `__file__` 计算项目根目录)
- 高德 API 密钥集中在 `config.py``geocoding.py``settings` 导入
- 端口号有 pydantic 校验 (ge=1, le=65535)
## 启动服务
```bash
# 启动服务
cd /home/gpsystem
nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8088 > server.log 2>&1 &
# 启动 FRP 客户端 (TCP隧道)
nohup /tmp/frpc -c /home/gpsystem/frpc.toml > /tmp/frpc.log 2>&1 &
# 检查服务状态
curl http://localhost:8088/health
curl http://localhost:8088/api/devices
```
## 管理后台 UI
访问 `/admin` 路径,包含以下页面:
1. **仪表盘** - 设备统计、告警概览、在线状态
2. **设备管理** - 添加/编辑/删除设备
3. **位置追踪** - Leaflet 地图、轨迹回放、地址显示
4. **告警管理** - 告警列表、确认处理、位置/地址显示
5. **考勤记录** - 上下班打卡记录 (0xB0/0xB1 GPS+LBS+WiFi)
6. **蓝牙记录** - 蓝牙打卡(0xB2)/定位(0xB3)记录含信标MAC/UUID/RSSI等
7. **信标管理** - 蓝牙信标注册、位置配置MAC/UUID/楼层/区域/经纬度)
8. **指令管理** - 发送指令/留言/TTS语音
## 协议详情
KKS 二进制协议,详见 `doc/KKS_Protocol_P240_P241.md`
### 已支持协议号
| 协议号 | 名称 | 方向 |
|--------|------|------|
| 0x01 | Login 登录 | 终端→服务器 |
| 0x13 | Heartbeat 心跳 | 双向 |
| 0x1F | Time Sync 时间同步 | 双向 |
| 0x22 | GPS 定位 | 终端→服务器 |
| 0x26 | Alarm ACK 告警确认 | 服务器→终端 |
| 0x28 | LBS 多基站 | 终端→服务器 |
| 0x2C | WiFi 定位 | 终端→服务器 |
| 0x36 | Heartbeat Extended 扩展心跳 | 双向 |
| 0x80 | Online Command 在线指令 | 服务器→终端 |
| 0x81 | Online Command Reply | 终端→服务器 |
| 0x82 | Message 留言 | 服务器→终端 |
| 0x94 | General Info (ICCID/IMSI等) | 双向 |
| 0xA0 | 4G GPS 定位 | 终端→服务器 |
| 0xA1 | 4G LBS 多基站 | 终端→服务器 |
| 0xA2 | 4G WiFi 定位 | 终端→服务器 |
| 0xA3-0xA5 | 4G 告警 (围栏/LBS) | 终端→服务器 |
| 0xA9 | WiFi 告警 | 终端→服务器 |
| 0xB0-0xB1 | 考勤打卡 | 双向 |
| 0xB2 | 蓝牙打卡 | 双向 |
| 0xB3 | 蓝牙定位 | 终端→服务器 |
### 数据包格式
```
[Start 0x7878/0x7979] [Length 1-2B] [Protocol 1B] [Content NB] [Serial 2B] [CRC 2B] [Stop 0x0D0A]
```
## 当前设备
| 字段 | 值 |
|------|-----|
| IMEI | 868120334031363 |
| 型号 | P241 |
| 名称 | 测试 |
| DB ID | 1 |
| ICCID | 89860848102570005286 |
| IMSI | 460240388355286 |
| SIM卡 | 中国移动 IoT (MCC=460, MNC=0) |
| 位置 | 成都地区 (~30.605°N, 103.936°E) |
## 重要实现细节
### IMEI 解析
- BCD 编码: 8字节 = 16 hex digits, 首位为填充 0
- 使用 `raw_hex[1:]` 只移除首位填充0 (不用 lstrip 避免移除多个)
### 设备登录
- 登录时不覆盖用户设置的 device_type (避免被覆盖为 raw hex)
- 重连时先关闭旧连接再建立新连接
- 断开连接时检查 writer 是否匹配,避免误将新连接标记为 offline
### 告警处理
- 告警响应使用 **0x26 ACK** (非原始协议号回复)
- 告警类型常量使用 **snake_case** (如 `sos`, `vibration`, `enter_fence`)
- 4G 告警包解析:
- **0xA3/0xA4** (围栏告警): datetime(6) + gps(12) + lbs_length(1) + MCC/MNC + LAC(4) + CellID(8) + terminal_info(1) + voltage_level(**1**字节) + gsm_signal(1) + alarm_code(1) + language(1)
- **0xA5** (LBS告警): **无datetime, 无GPS** — 直接从 MCC 开始
- **0xA9** (WiFi告警): datetime(6) + MCC/MNC + cell_type(1) + cell_count(1) + 基站列表 + timing_advance(1) + WiFi列表 + alarm_code(1) + language(1)
- MCC 高位 `0x8000` 标志: 表示 MNC 为 2 字节 (非默认1字节)
- voltage_level 是 **1字节** (0x00-0x06 等级)不是2字节毫伏值
- LBS/WiFi 报警自动执行前向地理编码(基站→经纬度)+ 逆地理编码(经纬度→地址)
### 位置数据与地理编码
- GPS 定位 (0x22/0xA0): 直接包含经纬度坐标,精度最高 (~10m)
- LBS 基站定位 (0x28/0xA1): 包含 MCC/MNC/LAC/CellID需要地理编码转换为经纬度
- WiFi 定位 (0x2C/0xA2): 包含基站数据 + WiFi AP MAC地址列表需要地理编码
- **所有地理编码服务统一使用高德 (Amap)**
- **前向地理编码** (`geocode_location`): 基站/WiFi → 经纬度
- **高德智能硬件定位**: `apilocate.amap.com/position`,需企业认证 (认证中,个人账号返回 10012)
- WiFi+基站混合定位精度 ~30m企业认证通过后自动生效
- **逆地理编码** (`reverse_geocode`): 经纬度 → 中文地址
- **高德**: `restapi.amap.com/v3/geocode/regeo`,需 WGS84→GCJ02 坐标转换 (服务端 Python 实现)
- 缓存策略: 坐标四舍五入到3位小数 (~100m) 作为缓存key
- 内置 LRU 缓存 (maxsize=10000),避免重复请求相同基站/坐标
- **WGS84→GCJ02 服务端转换**: geocoding.py 内置 `wgs84_to_gcj02()` 函数 (与前端 JS 版一致)
- **高德数字签名**: 参数按key排序拼接 + AMAP_SECRET → MD5 → sig 参数
### API 认证与限流
- **认证**: 设置 `API_KEY` 环境变量后,所有 `/api/` 请求需携带 `X-API-Key` 请求头
- **多 API Key**: 支持 master key (环境变量) + 数据库管理的 API Key (SHA-256 hash)
- **权限级别**: `read` (只读) < `write` (读写) < `admin` (管理,含 key 管理)
- **权限控制**: 所有 POST/PUT/DELETE 端点需要 `write` 权限,`/api/keys` 需要 `admin` 权限
- **Key 管理**: `POST /api/keys` 创建 key (返回明文一次), `GET /api/keys` 列表, `PUT /api/keys/{id}` 更新, `DELETE /api/keys/{id}` 停用
- **限流**: 全局 60/min (default_limits),写操作 30/min (`@limiter.limit`)
- **真实 IP**: 从 `X-Forwarded-For``CF-Connecting-IP``request.client.host` 提取
- **CORS**: `CORS_ORIGINS=*` 时自动禁用 `allow_credentials`
### WebSocket 实时推送
- **端点**: `ws://host/ws?api_key=xxx&topics=location,alarm`
- **Topics**: location, alarm, device_status, attendance, bluetooth
- **认证**: query param api_key (支持 master key + DB key)
- **最大连接**: 100 个 WebSocket 连接
- **消息格式**: JSON `{"topic": "...", "data": {...}, "timestamp": "..."}`
- **广播点**: 位置存储、报警存储、设备上下线、考勤存储、蓝牙存储
- **Ping/Pong**: 客户端发送 "ping" 保活,服务器回复 "pong"
### 数据清理
- **自动清理**: 后台定时任务,每 `DATA_CLEANUP_INTERVAL_HOURS`(默认24) 小时执行
- **保留天数**: `DATA_RETENTION_DAYS`(默认90) 天,删除过期的 location/heartbeat/alarm/attendance/bluetooth 记录
- **手动清理**: `POST /api/system/cleanup` (admin only)
### 批量操作 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/locations/batch-latest` — 批量获取多设备最新位置 (最多100)
- `GET /api/devices/all-latest-locations` — 获取所有在线设备最新位置
- `POST /api/commands/batch` — 批量发送指令 (最多100)`model_validator` 互斥校验
- 所有批量操作使用 WHERE IN 批量查询,避免 N+1
### API 分页
- page_size 最大限制为 100 (schema 层校验)
- 前端设备选择器使用 page_size=100 (不能超过限制)
### CRC 算法
- CRC-ITU / CRC-16/X-25
- Reflected polynomial: 0x8408
- Initial value: 0xFFFF
- Final XOR: 0xFFFF
### TCP 指令下发
- 0x80 (Online Command): ASCII 指令 + 2字节语言字段 (`0x0001`=中文)
- 0x81 (Command Reply): 设备回复,格式 length(1) + server_flag(4) + content(n) + language(2)
- 0x82 (Message): **UTF-16 BE** 编码的文本消息 (非UTF-8)
- 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等待设备重新登录
### 蓝牙信标管理
- **BeaconConfig 表**: 注册蓝牙信标,配置 MAC/UUID/Major/Minor/楼层/区域/经纬度/地址
- **自动关联**: 0xB2 打卡和 0xB3 定位时,根据 beacon_mac 查询 beacon_configs 表
- **位置写入**: 将已注册信标的经纬度写入 BluetoothRecord 的 latitude/longitude
- **多信标定位**: 0xB3 多信标场景,取 RSSI 信号最强的已注册信标位置
- **设备端配置**: 需通过 0x80 指令发送 `BTON#` 开启蓝牙、`BTSCAN,1#` 开启扫描
- **已知有效指令**: `BTON#`(btON:1), `BTSCAN,1#`(btSCAN:1) — 设备确认设置成功
- **当前状态**: ✅ 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 子协议
- 子协议 0x0A: IMEI(8字节) + IMSI(8字节) + ICCID(10字节)
- 子协议 0x09: GPS 卫星状态
- 子协议 0x00: ICCID(10字节)
- 子协议 0x04: 设备配置上报 `ALM2=40;ALM4=E0;MODE=03;IMSI=...`
### 前端字段映射 (admin.html)
- 设备信号: `d.gsm_signal` (非 `d.signal_strength`)
- 指令响应: `c.response_content` (非 `c.response`)
- 响应时间: `c.response_at || c.sent_at` (非 `c.updated_at`)
- 位置地址: `l.address` (高德逆地理编码结果)
- 卫星数: `l.gps_satellites` (非 `l.accuracy`)
- 记录时间: `l.recorded_at` (非 `l.timestamp`)
- 报警来源: `a.alarm_source` (非 `a.source`)
- 报警信号: `a.gsm_signal` (非 `a.signal_strength`)
- 报警类型: snake_case (如 `vibration`, `power_cut`, `voice_alarm`)
## FRP 配置
```toml
# frpc.toml (容器端)
serverAddr = "152.69.205.186"
serverPort = 7000
auth.method = "token"
auth.token = "PassWord0325"
[[proxies]]
name = "badge-tcp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5000
remotePort = 5001
```
## 外部服务器
- IP: 152.69.205.186
- OS: Ubuntu 20.04, Python 3.11, 24GB RAM
- FRP Server: frps v0.62.1 (1panel Docker, network_mode: host)
- Config: /opt/1panel/apps/frps/frps/data/frps.toml
## 高德地图 API
### Key
- **Web服务 Key**: `a9f4e04f5c8e739e5efb07175333f30b`
- **安全密钥**: `bfc4e002c49ab5f47df71e0aeaa086a5`
- **账号类型**: 个人认证开发者 (正在申请企业认证)
### 已接入服务
- **✅ 逆地理编码** (`restapi.amap.com/v3/geocode/regeo`): 经纬度 → 地址文本,服务端 WGS84→GCJ02 坐标转换
- **✅ 智能硬件定位** (`apilocate.amap.com/position`): WiFi+基站 → 经纬度 (代码就绪,企业认证通过前返回 10012)
- **✅ 前端地图瓦片**: 高德瓦片 (GCJ-02, 标准Mercator),前端 WGS84→GCJ02 坐标转换
- **数字签名**: `_amap_sign()` — 参数按key排序拼接 + AMAP_SECRET → MD5 → sig 参数
### 配额 (个人认证开发者)
- 基础LBS服务: 5,000 次/日 (逆地理编码等)
- 在线定位: 50,000 次/日 (企业认证后 1,000,000 次/日)
## 已知限制
1. **IoT SIM 卡不支持 SMS** - 144 号段的物联网卡无法收发短信,需通过平台或 TCP 连接配置设备
2. **Cloudflare Tunnel 仅代理 HTTP** - TCP 流量必须通过 FRP 转发
3. **SQLite 单写** - 高并发场景需切换 PostgreSQL
4. **设备最多 100 台列表** - 受 page_size 限制,超过需翻页查询
5. **基站/WiFi定位需企业认证** - 高德智能硬件定位 API 已接入但个人账号返回 10012企业认证通过后自动生效
## 已修复的问题 (Bug Fix 记录)
### 数据链路修复
1. **IMEI 解析** - 修复 BCD 解码逻辑,确保正确提取 15 位 IMEI
2. **设备类型覆盖** - 登录时不再覆盖用户设置的 device_type
3. **设备重连** - 新连接建立前先关闭旧连接,断开时检查 writer 身份
4. **告警类型提取** - 修正 alarm byte 位置,从 terminal_info+voltage+gsm_signal 之后读取
### 协议修复
5. **0x80 指令包** - 添加缺失的 2 字节语言字段 (`0x0001`)
6. **0x82 消息编码** - 从 UTF-8 改为 UTF-16 BE (协议要求)
7. **0x94 通用信息** - 完善子协议 0x0A (IMEI+IMSI+ICCID) 和 0x09 (GPS卫星状态) 解析
### 前端修复
8. **设备选择器为空** - `page_size=500` 超过 API 最大限制 100返回 422 错误
9. **位置页面崩溃** - API 返回 `data: null` 时未做空值检查
10. **轨迹查询失败** - 缺少必填的 start_time/end_time 参数
11. **字段名不匹配** - 修复 signal_strength→gsm_signal, response→response_content, source→alarm_source 等
### 定位功能修复
12. **WiFi/LBS 无坐标** - 添加 wifi/wifi_4g 解析分支 (原代码缺失)
13. **地理编码集成** - LBS/WiFi 定位数据自动转换为经纬度坐标
14. **邻近基站和WiFi数据** - 存储到 LocationRecord 的 neighbor_cells 和 wifi_data 字段
### 告警功能修复
15. **告警响应协议** - 改为使用 0x26 ACK 响应 (原来错误使用地址回复)
16. **voltage_level 解析** - 从2字节改为1字节 (0x00-0x06等级)
17. **0xA5 LBS告警格式** - 移除错误的 datetime/lbs_length 前缀,直接从 MCC 开始
18. **0xA9 WiFi告警** - 独立解析器 (非GPS格式),支持 cell_type/cell_count/WiFi AP 列表
19. **MNC 2字节标志** - `_parse_mcc_mnc_from_content` 正确处理 MCC 高位 0x8000 标志
20. **告警类型常量** - 改为 snake_case新增 voice_alarm(0x16), fake_base_station(0x17), cover_open(0x18), internal_low_battery(0x19)
21. **LBS/WiFi 报警定位** - 为无GPS的报警添加前向地理编码
### 逆地理编码
22. **逆地理编码集成** - 位置和报警记录自动获取中文地址 (高德逆地理编码)
23. **地址字段** - LocationRecord 新增 address 字段,前端位置表/报警表/地图弹窗显示地址
### 蓝牙与考勤功能
24. **0xB0/0xB1 考勤解析** - 完整解析 GPS+LBS+WiFi 考勤包含基站邻区、WiFi AP、前向+逆地理编码
25. **0xB2 蓝牙打卡解析** - 解析 iBeacon 数据 (RSSI/MAC/UUID/Major/Minor/电池/打卡类型)
26. **0xB3 蓝牙定位解析** - 解析多信标数据,每信标 30 字节,支持电压(V)/百分比(%)电池单位
27. **考勤类型检测** - 从 terminal_info 字节 bits[5:2] 判断 clock_in(0001)/clock_out(0010)
28. **信标管理系统** - BeaconConfig CRUD注册信标物理位置TCP 自动关联信标经纬度
### 指令发送修复
29. **server_flag_str 未定义** - send_command 中记录日志时变量未定义导致 NameError
30. **TCP 层重复创建日志** - 简化 send_command/send_message 只负责发送CommandLog 由 API 层管理
31. **IMEI lstrip 问题** - `lstrip("0")` 可能删除多个前导零,改为 `raw_hex[1:]`
32. **设备启动状态** - 服务器启动时重置所有设备为 offline避免显示在线但实际未连接
33. **0x81 回复解析** - 去除末尾 2 字节语言字段,正确提取回复文本
### 协议全面审计修复 (2026-03-16)
34. **0x1F 时间同步格式** - 从6字节(YY MM DD HH MM SS)改为4字节Unix时间戳+2字节语言字段
35. **地址回复完整格式** - 重写 `_send_address_reply`,实现完整格式: server_flag(4)+ADDRESS/ALARMSMS(7-8)+&&+UTF16BE地址+&&+电话(21)+##
36. **报警地址回复** - 报警包在发送0x26 ACK后额外发送0x17地址回复(含ALARMSMS标记)
37. **0x82 留言格式** - 添加 server_flag(4字节) + language(2字节) 字段
38. **0x22 GPS Cell ID** - 从2字节改为3字节解析 (2G基站CellID为3字节)
39. **登录包时区/语言** - 解析 content[10:12] 提取时区(bit15-04)和语言(bit00)存入Device
40. **0x94 不需回复** - 移除服务器响应,从 PROTOCOLS_REQUIRING_RESPONSE 中删除
41. **WiFi缓存+LRU淘汰** - 新增 LRUCache(OrderedDict, maxsize=10000)替换所有dict缓存修复WiFi缓存未写入
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. **PROTO_ADDRESS_REPLY_EN 未导入** - 0xA5 报警地址回复时 NameError补充 import
52. **心跳自动恢复 online** - 心跳处理时自动将设备 status 设为 online + 重新注册 connections
### 全面切换高德 API (2026-03-23)
53. **高德逆地理编码** - 接入 restapi.amap.com/v3/geocode/regeo服务端 WGS84→GCJ02 坐标转换
54. **高德智能硬件定位** - 接入 apilocate.amap.com/position (基站+WiFi定位企业认证通过前返回 10012)
55. **高德数字签名** - 实现 `_amap_sign()` 函数 (参数排序 + AMAP_SECRET + MD5)
56. **服务端坐标转换** - geocoding.py 内置 `wgs84_to_gcj02()` Python 实现
57. **高德地图瓦片** - 前端替换为高德瓦片 (GCJ-02, 标准Mercator),前端 WGS84→GCJ02 坐标转换
58. **移除第三方地理编码** - 清理天地图/百度/Google/Unwired/Mylnikov统一使用高德
### API 安全加固 & 批量管理 (2026-03-20)
59. **API Key 认证** - `dependencies.py` 实现 X-API-Key 头认证,`secrets.compare_digest` 防时序攻击
60. **CORS + 限流** - `SlowAPIMiddleware` 全局限流 (60/min),写操作独立限速 (30/min)
61. **限流器真实 IP** - `extensions.py``X-Forwarded-For` / `CF-Connecting-IP` 提取真实客户端 IP
62. **全局异常处理** - 拦截未处理异常返回 500不泄露堆栈放行 HTTPException/ValidationError
63. **Schema 校验加强** - IMEI pattern、经纬度范围、Literal 枚举、command max_length、BeaconConfig MAC/UUID pattern
64. **Health 端点增强** - `/health` 检测数据库连通性 + TCP 连接设备数
65. **批量设备创建** - `POST /api/devices/batch` (最多500台)WHERE IN 单次查询去重,输入列表内 IMEI 去重
66. **批量设备更新** - `PUT /api/devices/batch`,单次查询 + 批量更新 + 单次 flush
67. **批量设备删除** - `POST /api/devices/batch-delete`,通过 body 传递避免 URL 长度限制
68. **批量指令发送** - `POST /api/commands/batch` (最多100台)`model_validator` 互斥校验 device_ids/imeis
69. **Heartbeats 路由** - 新增 `GET /api/heartbeats` 心跳记录查询 + 按 ID 获取
70. **按 ID 查询端点** - locations/{id}, attendance/{id}, bluetooth/{id} 放在路由末尾避免冲突
71. **Beacons double-commit 修复** - 移除 router 层多余的 flush/refresh依赖 service 层
### 全面改进 (2026-03-22)
#### Protocol 修复
72. **parser.py 0x22 CellID** - 从2字节改为3字节解析
73. **parser.py 0xA5 LBS告警** - 移除错误的 datetime+lbs_length 前缀
74. **parser.py 0xA9 WiFi告警** - 完全重写为 datetime+MCC/MNC+cell_type+cell_count+基站+TA+WiFi+alarm_code
75. **parser.py voltage** - 0xA3/0xA5/0xA9 voltage 从2字节改为1字节
76. **parser.py 0xA4** - 新增多围栏告警解析器 (含 fence_id)
77. **parser.py 0xB2** - 完整 iBeacon 解析 (RSSI/MAC/UUID/Major/Minor/Battery)
78. **parser.py 0xB3** - beacon_count + 每信标30字节解析
79. **parser.py 0xB0/0xB1** - 补充 gps_positioned/terminal_info/voltage/gsm_signal 等中间字段
80. **parser.py 0x81** - 新增指令回复解析器
81. **builder.py 0x1F** - 时间同步添加2字节 language 参数
82. **builder.py 地址回复** - 重写 CN/EN 地址回复完整格式
83. **builder.py 0x80/0x82** - cmd_len 正确包含 language(2)
84. **constants.py** - 从 PROTOCOLS_REQUIRING_RESPONSE 移除 0x28
#### 批量 API + 索引
85. **batch-latest** - `POST /api/locations/batch-latest` 批量获取多设备最新位置
86. **all-latest-locations** - `GET /api/devices/all-latest-locations` 所有在线设备位置
87. **数据库索引** - 新增5个索引 (alarm_type, acknowledged, beacon_mac, location_type, attendance_type)
#### 多 API Key + 权限控制
88. **ApiKey 模型** - SHA-256 hash, permissions(read/write/admin), is_active
89. **多 Key 认证** - master key (env) + DB keys, last_used_at 追踪
90. **权限控制** - require_write/require_admin 依赖,写端点需 write 权限
91. **Key 管理 API** - `/api/keys` CRUD (admin only),创建时返回明文一次
92. **DeviceUpdate** - 移除 status 字段 (设备状态由系统管理)
#### WebSocket 实时推送
93. **WebSocketManager** - 连接管理器topic 订阅最大100连接
94. **WS 端点** - `/ws?api_key=xxx&topics=location,alarm` WebSocket 认证
95. **TCP 广播集成** - 7个广播点 (location, alarm, device_status x2, attendance, bluetooth x2)
#### 数据清理 + 补充
96. **自动数据清理** - 后台定时任务DATA_RETENTION_DAYS=90, DATA_CLEANUP_INTERVAL_HOURS=24
97. **手动清理 API** - `POST /api/system/cleanup` (admin only)
98. **alarm_source 过滤** - alarms 列表新增 alarm_source 参数
99. **beacon_mac 过滤** - bluetooth 列表新增 beacon_mac 参数
100. **command_type 过滤** - commands 列表新增 command_type 参数
101. **sort_order 参数** - alarms/bluetooth 列表支持 asc/desc 排序
#### 协议层统一
102. **PacketBuilder 统一** - tcp_server.py 内嵌 PacketBuilder 改为委托 protocol/builder.py
### 审计修复 (2026-03-23)
103. **require_admin 导入** - main.py 缺少 require_admin 导入,设置 API_KEY 后 cleanup 端点崩溃
104. **0x28/0x2C CellID 3字节** - tcp_server.py 2G 基站 CellID 从2字节修正为3字节 (0x28 LBS, 0x2C WiFi, 邻区解析)
105. **parser.py CellID 3字节** - parse_lbs_station 默认 cell_id_size=2→3新增3字节分支; _parse_lbs_address_req CellID 2→3字节
106. **alarm_source 字段长度** - models.py String(10)→String(20), schemas.py max_length=10→20 ("single_fence"=12字符)
107. **AlarmRecord wifi_data/fence_data** - 0xA9 WiFi报警存储 wifi_data; 0xA4 多围栏报警提取并存储 fence_id
108. **CommandLog.sent_at** - 指令发送成功后设置 sent_at 时间戳 (原来始终为 NULL)
109. **geocoding IMEI 参数化** - 移除硬编码 IMEI新增 GEOCODING_DEFAULT_IMEI 配置项geocode_location 接受 imei 参数TCP 层传递设备 IMEI
110. **parser.py 循环长度检查** - _parse_lbs_multi 和 _parse_wifi 站点循环检查从 pos+5 改为 pos+6 (LAC=2+CellID=3+RSSI=1=6)
111. **batch sent_at** - batch_send_command 批量指令路径也设置 cmd_log.sent_at (与单条路径一致)
112. **GPS 经度符号反转** - course_status bit 11 含义为 0=东经/1=西经 (非 1=东经)`is_east` 改为 `is_west`,修复成都定位到北美洲的问题 (tcp_server.py + parser.py)
## 待完成功能
1. **心跳扩展模块解析** - 计步器、外部电压等模块未解析
2. **前端 WebSocket 集成** - admin.html Dashboard 改用 WebSocket 替代 30s 轮询,报警页实时通知
3. **协议层深度统一** - tcp_server.py 辅助方法 (_parse_gps, _parse_datetime 等) 逐步迁移到 protocol/parser.py
## 调试技巧
```bash
# 查看实时日志
tail -f /home/gpsystem/server.log | grep -aE "TCP|login|heartbeat|error|geocod|Amap" --line-buffered
# 检查数据库
python3 -c "
import sqlite3
conn = sqlite3.connect('/home/gpsystem/badge_admin.db')
cur = conn.cursor()
cur.execute('SELECT id, imei, device_type, status, battery_level FROM devices')
for row in cur.fetchall(): print(row)
cur.execute('SELECT id, location_type, latitude, longitude, address, recorded_at FROM location_records ORDER BY id DESC LIMIT 5')
for row in cur.fetchall(): print(row)
"
# 测试 API
curl -s http://localhost:8088/api/devices | python3 -m json.tool
curl -s http://localhost:8088/api/locations/latest/1 | python3 -m json.tool
curl -s http://localhost:8088/health
# 发送指令
curl -s -X POST http://localhost:8088/api/commands/send \
-H "Content-Type: application/json" \
-d '{"device_id":1,"command_type":"online_cmd","command_content":"GPSON#"}' | python3 -m json.tool
# 开启蓝牙扫描
curl -s -X POST http://localhost:8088/api/commands/send \
-H "Content-Type: application/json" \
-d '{"device_id":1,"command_type":"online_cmd","command_content":"BTON#"}' | python3 -m json.tool
# 管理信标
curl -s http://localhost:8088/api/beacons | python3 -m json.tool
curl -s -X POST http://localhost:8088/api/beacons \
-H "Content-Type: application/json" \
-d '{"beacon_mac":"AA:BB:CC:DD:EE:FF","name":"前台","floor":"1F","latitude":30.27,"longitude":120.15}' | python3 -m json.tool
# 检查 FRP 连接
ps aux | grep frpc
# FRP Dashboard: http://152.69.205.186:7500 (admin/PassWord0325)
# 检查端口
python3 -c "
with open('/proc/net/tcp') as f:
for line in f:
parts = line.split()
if len(parts)>=2 and ':' in parts[1]:
port = int(parts[1].split(':')[1], 16)
if port in (8088, 5000): print(f'Port {port}: listening')
"
# 测试地理编码
python3 -c "
import asyncio
from app.geocoding import geocode_location, reverse_geocode
async def test():
lat, lon = await geocode_location(mcc=460, mnc=0, lac=32775, cell_id=205098688)
print(f'lat={lat}, lon={lon}')
if lat: print(await reverse_geocode(lat, lon))
asyncio.run(test())
"
```