- routers注释更新: 补充/stats、/batch-acknowledge、/report、/broadcast等新端点 - 管理后台UI描述更新: 各页面统计面板、批量确认、轨迹摘要等新功能 - main.py描述更新: 补充system/overview和system/cleanup端点 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
958 lines
62 KiB
Markdown
958 lines
62 KiB
Markdown
# 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服务器, /api/system/overview, /api/system/cleanup
|
||
│ ├── 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, FenceConfig, DeviceFenceBinding, DeviceFenceState, CommandLog, ApiKey)
|
||
│ ├── schemas.py # Pydantic 请求/响应模型
|
||
│ ├── tcp_server.py # TCP 服务器核心 (~2400行), 管理设备连接、协议处理、数据持久化
|
||
│ ├── geocoding.py # 高德地理编码服务 (IoT v5 定位 + 逆地理编码 + legacy回退)
|
||
│ │
|
||
│ ├── 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
|
||
│ │ ├── fence_service.py # 电子围栏 CRUD + 设备绑定管理
|
||
│ │ ├── fence_checker.py # 围栏自动考勤引擎 (几何判定+状态机+自动打卡)
|
||
│ │ └── 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, /broadcast, /stats)
|
||
│ │ ├── locations.py # /api/locations (含 /stats, /track-summary/{id}, /latest, /batch-latest, /track, /batch-delete, /delete-no-coords)
|
||
│ │ ├── alarms.py # /api/alarms (含 /stats增强, /batch-acknowledge, /batch-delete, acknowledge, alarm_source过滤)
|
||
│ │ ├── attendance.py # /api/attendance (含 /stats增强, /report, /device/{id}, attendance_source过滤)
|
||
│ │ ├── bluetooth.py # /api/bluetooth (含 /stats, beacon_mac过滤, /batch-delete)
|
||
│ │ ├── heartbeats.py # /api/heartbeats (含 /stats, /batch-delete, 心跳记录查询)
|
||
│ │ ├── beacons.py # /api/beacons (信标管理 CRUD)
|
||
│ │ ├── fences.py # /api/fences (含 /stats, /{id}/events, /all-active, 设备绑定CRUD)
|
||
│ │ ├── geocoding.py # /api/geocode (POI搜索代理 /search, 逆地理编码 /reverse)
|
||
│ │ ├── api_keys.py # /api/keys (API密钥管理 CRUD, admin only)
|
||
│ │ └── ws.py # /ws (WebSocket实时推送, topic订阅)
|
||
│ │
|
||
│ └── static/
|
||
│ └── admin.html # 管理后台 SPA (暗色主题, 10个页面)
|
||
│
|
||
└── 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` 导入 (含 AMAP_HARDWARE_SECRET)
|
||
- 端口号有 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. **仪表盘** - 设备统计、今日告警/考勤/定位、在线率%、告警类型饼图、最近告警 (调用 system/overview + devices/stats + alarms/stats)
|
||
2. **设备管理** - 添加/编辑/删除设备、定位模式列、点击排序、快捷指令按钮、广播指令
|
||
3. **位置追踪** - 高德 JS API 2.0 3D 地图、轨迹回放+摘要(距离/时长/速度)、地址显示、左侧设备面板、低精度过滤、全部设备总览Tab
|
||
4. **告警管理** - 告警列表、单条/批量确认、今日计数、告警类型饼图、位置/地址显示
|
||
5. **考勤记录** - 打卡记录+今日计数、按来源筛选(设备/蓝牙/围栏) (0xB0/0xB1 GPS+LBS+WiFi)
|
||
6. **蓝牙记录** - 统计面板(总记录/打卡/定位/信标数)、蓝牙打卡(0xB2)/定位(0xB3)记录,含信标MAC/UUID/RSSI等
|
||
7. **信标管理** - 蓝牙信标注册、位置配置(MAC/UUID/楼层/区域/经纬度)、左侧信标面板
|
||
8. **电子围栏** - 统计面板(总围栏/启用/绑定/今日事件)、围栏管理(圆形/多边形/矩形)、地图绘制、POI搜索、左侧围栏面板、顶部Tab切换(围栏管理/设备绑定)
|
||
9. **数据日志** - 多类型数据记录统一查询 (位置/心跳/告警/考勤/蓝牙)
|
||
10. **指令管理** - 统计面板(总数/成功率/已发送/失败)、发送指令/留言/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]
|
||
```
|
||
- `0x7878`: 包长度占 1 字节
|
||
- `0x7979`: 包长度占 2 字节 (用于 0xB3 蓝牙定位等大包)
|
||
|
||
### GPS 坐标编码 (协议文档)
|
||
- **纬度/经度**: 4字节大端序无符号整数 ÷ 1,800,000 = 度数
|
||
- **坐标系**: WGS-84 (GPS芯片原生输出)
|
||
- **航向/状态** (2字节): Bit15-14保留, Bit13=实时差分, Bit12=GPS定位, Bit11=0东经/1西经, Bit10=0南纬/1北纬, Bit9-0=航向(0-360度)
|
||
|
||
### 2G vs 4G 协议差异
|
||
| 字段 | 2G (0x22/0x28/0x2C/0xB0) | 4G (0xA0/0xA1/0xA2/0xB1) |
|
||
|------|--------------------------|--------------------------|
|
||
| MNC | 1 或 2 字节 (MCC bit15) | 固定 2 字节 |
|
||
| LAC | 2 字节 | 4 字节 |
|
||
| Cell ID | 3 字节 | 8 字节 |
|
||
| 邻区 LAC | 2 字节 | 4 字节 |
|
||
| 邻区 Cell ID | 3 字节 | 8 字节 |
|
||
|
||
### 告警代码 (alarm_code, 协议文档)
|
||
| 代码 | 名称 | 代码 | 名称 |
|
||
|------|------|------|------|
|
||
| 0x00 | 正常 | 0x0C | 开机报警 |
|
||
| 0x01 | SOS求救 | 0x0D | GPS首次定位 |
|
||
| 0x02 | 断电报警 | 0x0E | 外电低电报警 |
|
||
| 0x03 | 震动报警 | 0x0F | 外电低电保护 |
|
||
| 0x04 | 进围栏报警 | 0x10 | 换卡报警 |
|
||
| 0x05 | 出围栏报警 | 0x11 | 关机报警 |
|
||
| 0x06 | 超速报警 | 0x12 | 低电飞行模式 |
|
||
| 0x09 | 位移报警 | 0x13 | 拆卸报警 |
|
||
| 0x0A | 进GPS盲区 | 0x14 | 门报警 |
|
||
| 0x0B | 出GPS盲区 | 0x15 | 低电关机报警 |
|
||
| — | — | 0x16 | 声控报警 |
|
||
| — | — | 0x17 | 伪基站报警 |
|
||
| — | — | 0x18 | 开盖报警 |
|
||
| — | — | 0x19 | 内部电池低电 |
|
||
|
||
### terminal_info 字节 (考勤包 0xB0/0xB1/0xB2)
|
||
- Bit[5:2]: 打卡/报警类型
|
||
- `0001` = 上班打卡 (clock_in)
|
||
- `0010` = 下班打卡 (clock_out)
|
||
- `0100` = 开机报警
|
||
- `0110` = 低电报警
|
||
- `1000` = SOS 报警
|
||
- `1010` = 进电子围栏
|
||
- `1100` = 出电子围栏
|
||
- `1110` = 关机报警
|
||
|
||
### 蓝牙包字段 (协议文档)
|
||
- **0xB2 蓝牙打卡**: datetime(6) + RSSI(1) + MAC(6) + UUID(16) + Major(2) + Minor(2) + 电量(2) + terminal_info(1) + 保留(2)
|
||
- **0xB3 蓝牙定位**: datetime(6) + beacon_count(1) + N×[RSSI(1) + MAC(6) + UUID(16) + Major(2) + Minor(2) + 电量(2) + 电量单位(1)] = 每信标30字节
|
||
- **电量单位**: 0=伏(值×0.01V), 1=百分比
|
||
|
||
### 考勤包字段差异 (协议文档)
|
||
- **0xB0**: datetime(6) + gps_positioned(1) + 保留(2) + GPS(12) + terminal_info(1) + voltage(1) + gsm(1) + 扩展(2) + **2G LBS+WiFi**
|
||
- **0xB1**: datetime(6) + gps_positioned(1) + 保留(2) + GPS(12) + terminal_info(1) + voltage(1) + gsm(1) + 扩展(2) + **4G LBS+WiFi**
|
||
- **响应格式**: datetime(6) + 状态(1, 1=成功) + 打卡类型(1, 1=上班/2=下班) + 保留(2)
|
||
|
||
## 当前设备
|
||
|
||
| 字段 | 值 |
|
||
|------|-----|
|
||
| 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/0xB0/0xB1) | **WGS-84** | 原始值 ÷ 1,800,000,GPS硬件原生输出 |
|
||
| 高德前向地理编码 (LBS/WiFi) | **GCJ-02** | 高德 API 返回的都是 GCJ-02 |
|
||
| 高德逆地理编码输入 | **GCJ-02** | 需将 WGS-84 转 GCJ-02 后查询 |
|
||
| 高德 JS API 前端地图 | **GCJ-02** | 高德地图原生使用 GCJ-02 |
|
||
| 数据库存储 | **统一 WGS-84** | 高德返回 GCJ-02 经 `gcj02_to_wgs84()` 反转后存储 |
|
||
|
||
#### 坐标统一方案 (已修复)
|
||
- **修复**: `geocoding.py` 的 v5 API (`_geocode_amap_v5`) 和旧版 API (`_geocode_amap_legacy`) 返回前均将高德 GCJ-02 坐标反转为 WGS-84
|
||
- **新增函数**: `gcj02_to_wgs84()` — 使用迭代近似法 (lat*2 - gcj_lat)
|
||
- **结果**: 数据库统一存储 WGS-84,前端/逆地理编码/围栏 统一做 WGS-84→GCJ-02 转换,无二次偏移
|
||
- **注意**: 修复前已存储的 LBS/WiFi 历史数据仍为 GCJ-02,新数据为 WGS-84
|
||
|
||
#### GPS 坐标解析 (协议文档)
|
||
- **格式**: 纬度(4字节) + 经度(4字节),大端序无符号整数
|
||
- **计算**: 十进制值 ÷ 1,800,000 = 度数
|
||
- **方向**: 由 course_status 字段的 bit 位决定:
|
||
- Bit 11: 0=东经, 1=西经
|
||
- Bit 10: 0=南纬, 1=北纬 (注意: 1=北, 与直觉相反)
|
||
- Bit 12: GPS 定位状态 (1=已定位)
|
||
- Bit 13: GPS 实时/差分定位
|
||
- Bit 9-0: 航向 (0-360度, 正北为0)
|
||
|
||
#### 2G vs 4G 基站字段差异 (协议文档)
|
||
| 字段 | 2G (0x22/0x28/0x2C/0xB0) | 4G (0xA0/0xA1/0xA2/0xB1) |
|
||
|------|--------------------------|--------------------------|
|
||
| MNC | 1 或 2 字节 (MCC bit15 决定) | 固定 2 字节 |
|
||
| LAC | 2 字节 | 4 字节 |
|
||
| Cell ID | 3 字节 | 8 字节 |
|
||
| 邻区 LAC | 2 字节 | 4 字节 |
|
||
| 邻区 Cell ID | 3 字节 | 8 字节 |
|
||
|
||
#### 地理编码服务
|
||
- **所有地理编码服务统一使用高德 (Amap)**
|
||
- **前向地理编码** (`geocode_location`): 基站/WiFi → 经纬度 (GCJ-02)
|
||
- **v5 IoT 定位 API** (主): `POST restapi.amap.com/v5/position/IoT`,使用 AMAP_KEY (企业订阅)
|
||
- accesstype: 1=移动网络, 2=WiFi (需2+个AP)
|
||
- **network**: `GSM`(2G) / `WCDMA`(3G/4G) / `NR`(5G) — **关键参数,`LTE` 不是有效值!** 4G 必须用 `WCDMA`
|
||
- **bts 格式**: `mcc,mnc,lac,cellid,signal,cage` (6字段,cage=信号新鲜度秒数,v5 必须包含)
|
||
- **4G LTE**: LAC 字段传 TAC,CellID 字段传 ECI (28位,>65535)
|
||
- WiFi 需 mmac (最强信号AP) + macs (其余AP),MAC 用冒号分隔小写
|
||
- diu 替代 imei 参数,show_fields=formatted_address 可直接返回地址
|
||
- POST body 方式提交,key 在 URL params,无需数字签名
|
||
- `location_type` 参数自动映射: lbs_4g/wifi_4g → network=WCDMA, lbs/wifi → network=GSM
|
||
- **旧版智能硬件定位 API** (回退): `GET apilocate.amap.com/position`,使用 AMAP_HARDWARE_KEY
|
||
- 仅当 v5 API 失败且 AMAP_HARDWARE_KEY 已配置时使用
|
||
- 使用 AMAP_HARDWARE_SECRET 签名 (与 AMAP_SECRET 独立)
|
||
- **重要**: bts 格式只能 5 字段 (`mcc,mnc,lac,cellid,signal`),**不能包含 cage 字段**,否则返回空位置
|
||
- **逆地理编码** (`reverse_geocode`): 经纬度 → 中文地址
|
||
- **高德**: `restapi.amap.com/v3/geocode/regeo`,使用 AMAP_KEY
|
||
- 输入需 WGS84→GCJ02 坐标转换 (服务端 Python 实现)
|
||
- 缓存策略: 坐标四舍五入到3位小数 (~100m) 作为缓存key
|
||
- **POI 搜索代理** (`/api/geocode/search`): 关键词搜索地点,返回 GCJ-02 坐标
|
||
- 代理 `restapi.amap.com/v3/place/text`,AMAP_KEY 不暴露到前端
|
||
- 用于围栏弹窗搜索地点功能
|
||
- 内置 LRU 缓存 (maxsize=10000),WiFi 和基站分别缓存,基站缓存 key 包含邻区基站 hash (有/无邻区精度不同)
|
||
- **WGS84→GCJ02 服务端转换**: geocoding.py 内置 `wgs84_to_gcj02()` 函数 (与前端 JS 版一致)
|
||
- **高德数字签名**: `_amap_sign()` — 参数按key排序拼接 + AMAP_SECRET → MD5 → sig 参数 (逆地理编码/POI搜索用)
|
||
|
||
### 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, fence_attendance
|
||
- **认证**: 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)
|
||
- `POST /api/locations/batch-delete` — 批量删除位置记录 (最多500),通过 body 传 location_ids
|
||
- `POST /api/locations/delete-no-coords` — 删除无坐标记录 (lat/lon为NULL),支持 device_id/时间范围过滤
|
||
- `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 信号最强的已注册信标位置
|
||
- **当前状态**: ✅ 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 智能电子工牌系列
|
||
|
||
### P241 蓝牙模式配置指南 (设备端)
|
||
设备蓝牙模式通过在线指令 (0x80) 配置,需依次完成以下步骤:
|
||
|
||
#### 步骤1: 设置打卡方式
|
||
- **指令**: `CLOCKWAY,A#`
|
||
- **参数 A**: `1`=仅手动打卡, `2`=仅蓝牙打卡, `3`=手动+蓝牙打卡 (推荐)
|
||
- **示例**: `CLOCKWAY,3#` → 回复 `setting OK.打卡方式:手动+蓝牙打卡`
|
||
|
||
#### 步骤2: 关闭/开启打卡才工作限制 (可选)
|
||
- **指令**: `BTWORKSW,ON#` 或 `BTWORKSW,OFF#`
|
||
- 设备默认需打卡后才开始工作,如不需要此限制可关闭
|
||
|
||
#### 步骤3: 设置工作模式
|
||
- **指令**: `MODE,A#`
|
||
- **参数 A**: `0`=计步定位, `1`=定时定位, `2`=蓝牙定位, `3`=智能模式(ZNMS)
|
||
- **示例**: `MODE,2#` → 回复 `setting OK.Bt`
|
||
|
||
#### 步骤4: 写入信标数据 (三种方式任选其一)
|
||
|
||
**方式1 — MAC 地址绑定**:
|
||
- **指令**: `BTMACSET,MAC1,MAC2,...#` 或 `BTMACSET1,MAC1,...#` ~ `BTMACSET4,MAC1,...#`
|
||
- 共 5 个序号 (默认+1~4),每个序号最多 10 个 MAC,总计最多 **50 个 MAC 地址**
|
||
- MAC 格式: 冒号分隔,如 `C3:00:00:34:43:5E`
|
||
- **示例**: `BTMACSET,C3:00:00:34:43:5E#` → 回复 `setting OK.bt mac address:1,C3:00:00:34:43:5E;`
|
||
|
||
**方式2 — UUID 绑定 (Major+Minor)**:
|
||
- **指令**: `BTUUIDSET,MAJOR,MINOR,MAJOR1,MINOR1,...#` 或 `BTUUIDSET1,...#` ~ `BTUUIDSET4,...#`
|
||
- 共 5 个序号,每个序号最多 10 个 UUID,总计最多 **50 个 UUID**
|
||
- **注意**: Major/Minor 需从十进制转换为十六进制
|
||
- **示例**: Major=10001(0x2711), Minor=19641(0x4CA9) → `BTUUIDSET,2711,4CA9#`
|
||
|
||
**方式3 — 仅 MAJOR 匹配**:
|
||
- **指令**: `ONLYMAJOR,ON/OFF,MAJOR#`
|
||
- ON 时蓝牙搜索只匹配 MAJOR(MAJOR 需十进制转十六进制)
|
||
- **示例**: `ONLYMAJOR,ON,2711#`
|
||
|
||
#### 步骤5: 语音播报开关
|
||
- **指令**: `BTMP3SW,A#`
|
||
- **参数 A**: `0`=关闭, `1`=开启
|
||
- 开启后设备识别到信标会语音播报"定位成功"
|
||
- **示例**: `BTMP3SW,1#` → 回复 `setting OK.BTWorkMp3SW:1`
|
||
|
||
#### 完整配置示例 (已验证 2026-03-30)
|
||
```bash
|
||
CLOCKWAY,3# # → setting OK.打卡方式:手动+蓝牙打卡
|
||
MODE,2# # → setting OK.Bt
|
||
BTMACSET,C3:00:00:34:43:5E# # → setting OK.bt mac address:1,C3:00:00:34:43:5E;
|
||
BTMP3SW,1# # → setting OK.BTWorkMp3SW:1
|
||
```
|
||
配置完成后,设备在每次心跳 (约5分钟) 扫描到已绑定信标时会语音播报"定位成功",并上报 0xB2 蓝牙打卡数据
|
||
|
||
#### 其他蓝牙相关指令
|
||
- `BTON#` → 开启蓝牙 (回复 `setting OK.btON:1`)
|
||
- `BTSCAN,1#` → 开启蓝牙扫描 (回复 `setting OK.btSCAN:1`)
|
||
|
||
### 电子围栏管理
|
||
- **FenceConfig 表**: 注册电子围栏,支持 circle/polygon/rectangle 三种类型
|
||
- **圆形围栏**: center_lat/center_lng (WGS-84) + radius (米)
|
||
- **多边形/矩形围栏**: points 字段存储 JSON `[[lng,lat], ...]` (WGS-84)
|
||
- **显示属性**: color (边框色), fill_color (填充色), fill_opacity (透明度), is_active (启用状态)
|
||
- **API 端点**: `/api/fences` CRUD + `/api/fences/all-active` 获取所有启用围栏
|
||
- **前端绘制**: 高德 MouseTool 组件,多边形默认 (左键添加顶点, 右键/双击完成), 圆形 (点击拖拽)
|
||
- **POI 搜索**: 围栏弹窗支持搜索地点并定位到搜索结果 (通过 `/api/geocode/search` 代理)
|
||
- **坐标转换**: 绘制完成后 GCJ-02→WGS-84 存储,显示时 WGS-84→GCJ-02 渲染
|
||
- **围栏面板**: 左侧面板显示围栏列表,支持显示/隐藏、编辑、绑定设备、定位到围栏
|
||
|
||
### 设备-围栏绑定与自动考勤
|
||
- **DeviceFenceBinding 表**: 多对多关系,绑定设备到围栏 (唯一约束 device_id+fence_id)
|
||
- **DeviceFenceState 表**: 状态机记录,跟踪每个设备-围栏对的进出状态 (is_inside, last_transition_at)
|
||
- **绑定 API**:
|
||
- `GET /api/fences/{id}/devices` — 获取围栏绑定的设备列表 (含进出状态)
|
||
- `POST /api/fences/{id}/devices` — 绑定设备到围栏 (body: device_ids, 幂等)
|
||
- `DELETE /api/fences/{id}/devices` — 解绑设备 (body: device_ids)
|
||
- **自动考勤引擎** (`fence_checker.py`):
|
||
- `haversine_distance()` — WGS-84 坐标间距离 (米)
|
||
- `is_inside_circle()` — 半径判定
|
||
- `is_inside_polygon()` — Ray-casting 射线法判定
|
||
- `check_device_fences()` — 主入口: 查询设备绑定的活跃围栏 → 几何判定 → 状态转换 → 自动打卡
|
||
- **自动打卡**: 进入围栏创建 clock_in、离开围栏创建 clock_out (AttendanceRecord, protocol_number=0, attendance_source="fence", lbs_data.source="fence_auto")
|
||
- **容差机制**: GPS=0m, WiFi=100m (FENCE_WIFI_TOLERANCE_METERS), LBS=200m (FENCE_LBS_TOLERANCE_METERS)
|
||
- **防抖**: FENCE_MIN_INSIDE_SECONDS=60,防止 GPS 抖动导致频繁进出
|
||
- **TCP 集成**: _store_location 存储位置后自动调用 check_device_fences,事件通过 WebSocket 广播 (fence_attendance + attendance topics)
|
||
- **WebSocket topic**: `fence_attendance` — 围栏进出事件实时推送
|
||
- **前端**: 围栏管理页面顶部 Tab 切换布局 — "围栏管理" Tab (地图+围栏列表) 和 "设备绑定" Tab (全屏绑定矩阵, 无地图)
|
||
- 围栏管理 Tab: 工具栏+地图+围栏表格,与原有布局一致
|
||
- 设备绑定 Tab: 独立工具栏(刷新/提示/保存)、全高绑定矩阵表格、表头sticky固定
|
||
|
||
### 考勤来源区分
|
||
- **attendance_source 字段**: 区分考勤记录来源,String(20),默认 "device"
|
||
- `device` — 设备硬件打卡 (0xB0/0xB1 协议)
|
||
- `bluetooth` — 蓝牙信标打卡 (0xB2 协议)
|
||
- `fence` — 围栏自动打卡 (fence_checker.py, protocol_number=0)
|
||
- **TCP 层**: 根据协议号自动设置 `_att_source = "bluetooth" if proto == 0xB2 else "device"`
|
||
- **围栏引擎**: fence_checker.py `_create_attendance()` 设置 `attendance_source="fence"`
|
||
- **前端**: 考勤记录表格增加"来源"列 (设备/蓝牙/围栏 图标),支持按来源筛选
|
||
- **API**: `GET /api/attendance` 新增 `attendance_source` 查询参数过滤
|
||
|
||
### 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`)
|
||
|
||
### 位置追踪低精度过滤
|
||
- **切换按钮**: "低精度" 按钮,点击后隐藏 LBS/WiFi 低精度定位点,仅显示 GPS
|
||
- **按钮状态**: 默认 `<i class="fas fa-eye"></i> 低精度` (灰色),激活后 `<i class="fas fa-eye-slash"></i> 低精度` (红色 #b91c1c)
|
||
- **影响范围**: 地图轨迹标记点 + 轨迹折线路径 + 记录表格行,三者联动
|
||
- **实时切换**: 已加载的轨迹和表格即时过滤,无需重新查询
|
||
- **判定逻辑**: `location_type` 以 `lbs` 或 `wifi` 开头视为低精度
|
||
- **轨迹标记颜色**: GPS=蓝色(#3b82f6), WiFi=青色(#06b6d4), LBS=橙色(#f59e0b), 蓝牙=紫色(#a855f7), 起点=绿色, 终点=红色
|
||
- **无精度圈**: 不绘制 LBS/WiFi 精度半径圆圈,仅通过标记颜色区分定位类型
|
||
|
||
## 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
|
||
| 名称 | Key | Secret | 用途 |
|
||
|------|-----|--------|------|
|
||
| 测试2 | `a9f4e04f5c8e739e5efb07175333f30b` | — | Web服务 (AMAP_KEY: IoT v5定位 + 逆地理编码 + POI搜索) |
|
||
| 测试 | `9c2fe56bb2bad44d238dd9b4be249e33` | `bfc4e002c49ab5f47df71e0aeaa086a5` | Web端 (JS API 2.0 3D地图, AMAP_SECRET用此key的secret) |
|
||
| 工牌 | `30eedee9d3c5cfcc43c3b27dd7654e3c` | — | 旧版智能硬件定位回退 (AMAP_HARDWARE_KEY, 可选) |
|
||
|
||
- **账号类型**: 企业认证开发者 (IoT 智能硬件订阅已开通)
|
||
|
||
### 已接入服务
|
||
- **✅ IoT 定位 v5** (`POST restapi.amap.com/v5/position/IoT`): WiFi+基站混合定位,使用 AMAP_KEY (企业订阅)
|
||
- **✅ 逆地理编码** (`restapi.amap.com/v3/geocode/regeo`): 经纬度 → 地址文本,服务端 WGS84→GCJ02 坐标转换,使用 AMAP_KEY
|
||
- **✅ POI 搜索** (`restapi.amap.com/v3/place/text`): 关键词搜索地点,服务端代理 (`/api/geocode/search`)
|
||
- **✅ 旧版智能硬件定位** (`apilocate.amap.com/position`): v5 失败时回退,使用 AMAP_HARDWARE_KEY
|
||
- **✅ 前端 3D 地图**: 高德 JS API 2.0 (GCJ-02),viewMode='3D',pitch=45,暗色主题,前端 WGS84→GCJ02 坐标转换
|
||
- **数字签名**: `_amap_sign()` — 参数按key排序拼接 + AMAP_SECRET → MD5 → sig 参数 (v5 API 不需签名)
|
||
|
||
### 配额 (企业认证开发者)
|
||
- 基础LBS服务: 5,000 次/日 (逆地理编码等)
|
||
- IoT 在线定位 v5: 1,000,000 次/日 (企业订阅)
|
||
|
||
## 已知限制
|
||
|
||
1. **IoT SIM 卡不支持 SMS** - 144 号段的物联网卡无法收发短信,需通过平台或 TCP 连接配置设备
|
||
2. **Cloudflare Tunnel 仅代理 HTTP** - TCP 流量必须通过 FRP 转发
|
||
3. **SQLite 单写** - 高并发场景需切换 PostgreSQL
|
||
4. **设备最多 100 台列表** - 受 page_size 限制,超过需翻页查询
|
||
5. **WiFi 定位精度依赖 AP 数量** - v5 API 要求至少 2 个 WiFi AP 才能使用 WiFi 定位模式,少于 2 个时回退为基站定位
|
||
|
||
## 已修复的问题 (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)
|
||
|
||
### 高德 IoT v5 API 升级 + 围栏功能 (2026-03-27)
|
||
|
||
#### 前向地理编码升级
|
||
113. **IoT v5 API** - 从旧版 `apilocate.amap.com/position` (GET) 升级为 `restapi.amap.com/v5/position/IoT` (POST),企业订阅已开通
|
||
114. **v5 WiFi 格式** - mmac (最强信号AP) + macs (其余AP),MAC 冒号分隔小写,accesstype=2 (WiFi)
|
||
115. **v5 回退机制** - v5 失败自动回退旧版 `apilocate.amap.com`,使用 AMAP_HARDWARE_KEY + AMAP_HARDWARE_SECRET 独立签名
|
||
116. **签名密钥分离** - 新增 AMAP_HARDWARE_SECRET 配置,旧版 API 不再错误使用 AMAP_SECRET 签名
|
||
117. **WiFi 缓存优化** - WiFi 和基站分别缓存,WiFi 缓存 key 使用排序后的 MAC 地址元组
|
||
|
||
#### 电子围栏模块
|
||
118. **FenceConfig 模型** - 新增 fence_configs 表 (circle/polygon/rectangle + 显示属性 + WGS-84 坐标)
|
||
119. **围栏 API** - fences.py router + fence_service.py service,完整 CRUD + all-active 端点
|
||
120. **POI 搜索代理** - 新增 `/api/geocode/search` 和 `/api/geocode/reverse`,AMAP_KEY 不暴露前端
|
||
|
||
#### 前端改进
|
||
121. **围栏管理页面** - 高德 MouseTool 绘制,多边形默认,左键添加顶点/右键完成,绘制后保留展示
|
||
122. **围栏 POI 搜索** - 弹窗内搜索框,搜索结果列表点击定位,服务端代理高德 API
|
||
123. **围栏地图展示** - 选中围栏后在地图上渲染 circle/polygon overlay,切换可见性
|
||
124. **位置追踪列表修复** - 修复位置记录列表不渲染数据的问题
|
||
125. **数据日志页面** - 新增统一数据查询页面,支持多类型记录查询
|
||
|
||
### 设备-围栏绑定与自动考勤 (2026-03-27)
|
||
126. **DeviceFenceBinding 模型** - 多对多绑定表 (device_id + fence_id 唯一约束, CASCADE 外键)
|
||
127. **DeviceFenceState 模型** - 状态机表 (is_inside, last_transition_at, last_check_at, 最后坐标)
|
||
128. **fence_checker.py** - 围栏自动考勤引擎: haversine 距离, ray-casting 多边形判定, 容差机制 (GPS=0m, WiFi=100m, LBS=200m), 防抖 (60s)
|
||
129. **绑定 CRUD** - fence_service.py 新增 get_fence_devices/get_device_fences/bind_devices_to_fence/unbind_devices_from_fence
|
||
130. **绑定 API** - fences.py 新增 GET/POST/DELETE /api/fences/{id}/devices 端点
|
||
131. **TCP 围栏集成** - _store_location 存储位置后自动检测围栏进出, 自动创建 AttendanceRecord (protocol_number=0, source=fence_auto)
|
||
132. **fence_attendance WebSocket** - 新增 topic, 围栏进出事件实时广播
|
||
133. **绑定设备 UI** - 围栏表格/面板添加绑定按钮, 弹窗管理已绑定设备 + 添加新绑定
|
||
|
||
### LBS 定位精度修复 (2026-03-30)
|
||
134. **v5 API network 参数修正** - `LTE` 不是有效 network 值 (返回 INVALID_PARAMS 20000),4G 改用 `WCDMA`;映射: lbs_4g/wifi_4g → WCDMA, lbs/wifi → GSM
|
||
135. **bts cage 字段条件化** - v5 API 需要 6 字段 bts (`...,cage`),旧版 API 只能 5 字段 (含 cage 返回空位置);新增 `_build_bts(include_cage)` 参数
|
||
136. **geocode_location 传递 location_type** - 新增 `location_type` 参数,TCP 三处调用 (位置/报警/考勤) 均传入,自动区分 2G(GSM)/4G(WCDMA)
|
||
137. **缓存 key 含邻区 hash** - 基站缓存 key 从 `(mcc, mnc, lac, cell_id)` 扩展为 `(mcc, mnc, lac, cell_id, nb_hash)`,修复有/无邻区基站返回同一缓存结果的精度问题
|
||
|
||
### 批量删除 + 考勤来源 (2026-03-30)
|
||
138. **批量删除位置记录** - 新增 `POST /api/locations/batch-delete` (最多500条),前端位置表格增加勾选框和"删除选中"按钮
|
||
139. **删除无坐标记录** - 新增 `POST /api/locations/delete-no-coords`,删除 lat/lon 为 NULL 的记录,支持 device_id/时间范围过滤,前端增加"清除无坐标"按钮
|
||
140. **attendance_source 字段** - AttendanceRecord 新增 `attendance_source` 字段 (device/bluetooth/fence),TCP 层根据协议号设置,围栏引擎设置 "fence"
|
||
141. **lbs_data schema 类型修复** - `lbs_data` 类型从 `list[dict]` 改为 `list[dict] | dict | None`,修复围栏打卡写入 dict 导致 pydantic ValidationError
|
||
142. **最新位置按钮发命令** - "最新位置"按钮改为发送 WHERE# 指令获取实时位置,每 2s 轮询最多 15s,显示倒计时
|
||
143. **围栏管理 Tab 布局** - 围栏页面从单表格改为 Tab 切换 ("围栏列表" + "设备绑定"),设备绑定 Tab 含围栏选择器、快速绑定/解绑、已绑定设备状态表
|
||
|
||
### 前端 UI 优化 (2026-03-30)
|
||
144. **围栏管理顶部 Tab** - Tab 从地图下方表格内移至页面顶部,"围栏管理" Tab 显示地图+围栏列表,"设备绑定" Tab 隐藏地图全屏展示绑定矩阵
|
||
145. **低精度定位过滤** - 位置追踪页面新增"低精度"切换按钮,隐藏 LBS/WiFi 点 (地图标记+轨迹折线+表格行联动过滤)
|
||
146. **移除精度圈** - 删除轨迹和最新位置视图中 LBS(1000m) 和 WiFi(80m) 的精度半径圆圈,仅通过标记颜色区分定位类型
|
||
|
||
### 设备管理 UI 增强 (2026-03-31)
|
||
147. **设备列表定位模式列** - 设备管理表格新增"定位模式"列,通过 batch-latest API 异步获取每设备最新 location_type,颜色编码 (GPS=蓝/WiFi=青/LBS=橙/蓝牙=紫)
|
||
148. **设备列表点击排序** - 表头可点击排序 (IMEI/名称/类型/状态/电量/信号/登录/心跳),默认按名称升序,支持中文自然排序 (numeric: true),▲/▼ 蓝色箭头指示
|
||
149. **设备列表快捷操作列** - 每行末尾新增 5 个带文字的快捷按钮: 定位(WHERE#)、GPS(GPSON#)、定时(MODE,1#)、状态(STATUS#)、重启(RESET#,红色+确认弹窗),离线设备按钮自动禁用
|
||
150. **IMEI 列蓝色可点击** - IMEI 列文字改为蓝色 (#60a5fa),视觉提示可点击进入设备详情
|
||
151. **广播指令按钮** - 工具栏刷新按钮右侧新增: "全部定位"、"全部开GPS"、"全部定时"、"全部智能"、"自定义广播" 按钮,发送给所有在线设备
|
||
152. **自定义广播弹窗** - 弹窗含指令输入框 + 常用指令快捷选择 (每个按钮带中文说明和 title 悬浮提示)
|
||
|
||
### 广播指令 API (2026-03-31)
|
||
153. **POST /api/commands/broadcast** - 广播指令给所有在线设备,自动查找 status=online 的设备,逐一通过 TCP 发送,返回 sent/failed 统计,跳过 TCP 未连接的设备
|
||
|
||
### 低精度过滤修正 (2026-03-31)
|
||
154. **低精度仅隐藏 LBS** - `_isLowPrecision()` 从匹配 `lbs|wifi` 改为仅匹配 `lbs`,WiFi 定位 (30~100m精度) 不再被隐藏
|
||
155. **低精度过滤后端联动** - 隐藏低精度时,后端请求加 `exclude_type=lbs` 参数,分页 total/页数准确反映过滤后数量
|
||
156. **exclude_type API 参数** - `GET /api/locations` 新增 `exclude_type` 查询参数,后端使用 `notin_` (非 LIKE) 实现索引友好的类型排除
|
||
157. **轨迹折线联动** - 隐藏 LBS 点时,折线跳过 LBS 点重新连接剩余 GPS/WiFi 点
|
||
|
||
### 性能优化与并发修复 (2026-03-31)
|
||
158. **SQLite WAL 模式** - `database.py` 启动时设置 `PRAGMA journal_mode=WAL; synchronous=NORMAL; cache_size=-64000; busy_timeout=5000`,读写并发不再互斥
|
||
159. **aiohttp Session 复用** - `geocoding.py` 新增 `_get_http_session()` 懒初始化共享 Session,消除每次地理编码的 TCP/TLS 握手开销,app shutdown 时 `close_http_session()`
|
||
160. **connections 字典加锁** - `tcp_server.py` TCPManager 新增 `_conn_lock = asyncio.Lock()`,login/cleanup/send_command/send_message 操作均在锁内执行,消除重连竞态条件
|
||
161. **device_id 缓存** - ConnectionInfo 新增 `device_id` 槽位,`_get_device_id()` 首次查询后缓存到连接信息,后续数据包不再查询 DB
|
||
162. **recv_buffer 改 bytearray** - TCP 接收缓冲区从 `bytes` 改为 `bytearray`,拼接操作从 O(N²) 降为摊销 O(1)
|
||
163. **WebSocket 广播并发化** - `websocket_manager.py` broadcast 从串行循环改为 `asyncio.gather()` 并发发送 + 3秒超时,慢客户端不再阻塞其他订阅者
|
||
164. **补充数据库索引** - 新增 `ix_alarm_source`, `ix_att_source`, `ix_att_device_type_time`, `ix_device_status`, `ix_fence_active` 共 5 个索引
|
||
165. **exclude_type 查询优化** - 从 `NOT LIKE 'lbs%'` 改为 `notin_(['lbs','lbs_4g'])`,可利用 `ix_loc_type` 索引
|
||
166. **地理编码日志降级** - Amap v5/legacy 请求/响应日志从 INFO 降为 DEBUG,减少高频同步日志对事件循环的阻塞
|
||
|
||
### 性能优化第二批 (2026-03-31)
|
||
167. **围栏 N+1 查询批量化** - `fence_checker.py` DeviceFenceState 从 per-fence 循环查询改为一次 `WHERE IN` 批量加载 `states_map`,N+1→1 查询
|
||
168. **考勤去重提前查询** - `_has_attendance_today` 从循环内调用改为循环前预查一次,多围栏场景避免重复查询
|
||
169. **地理编码并行化** - `_store_location` 中 reverse_geocode 改为 `asyncio.ensure_future` 与 DB 操作并行,减少串行等待
|
||
170. **围栏检查复用会话** - 围栏检查从独立开 `async_session` 改为与位置存储共用同一个 session/transaction,减半 DB 连接开销
|
||
171. **API Key 认证缓存** - `dependencies.py` 新增 `_AUTH_CACHE` 内存缓存 (TTL=60s),DB 查询+flush 仅在 cache miss 时执行
|
||
172. **TCP 空闲超时** - `config.py` 新增 `TCP_IDLE_TIMEOUT=600` (默认10分钟),`reader.read` 包裹 `asyncio.wait_for`,超时自动断开清理连接
|
||
173. **admin.html 内存缓存** - 启动时一次性读入内存,`/admin` 请求不再每次读磁盘 (1.2ms vs ~5ms)
|
||
174. **SQLite 外键约束** - `database.py` 启动时 `PRAGMA foreign_keys=ON`,确保围栏删除时级联清理绑定/状态数据
|
||
|
||
### 前端 IMEI 显示 + 位置追踪增强 (2026-03-31)
|
||
175. **全局 device_id→IMEI 映射** - `_devIdToImei` 全局映射 + `_imei()` 辅助函数,设备列表加载时自动构建
|
||
176. **表格设备列改显示 IMEI** - 告警管理、考勤记录、蓝牙记录、指令管理、位置追踪、数据日志、仪表盘告警摘要全部从 device_id 改为 IMEI 显示
|
||
177. **地图标记悬浮弹窗** - 轨迹点/最新位置/总览标记改为 mouseover 自动弹出、mouseout 自动关闭,保留 click 打开
|
||
178. **位置追踪 Tab 切换** - 位置追踪页面顶部新增 Tab: "轨迹追踪" (原有功能) + "全部设备总览" (新功能)
|
||
179. **全部设备总览** - 左侧设备选择面板 (勾选/取消/全选,显示名称+IMEI+在线状态) + 右侧地图展示所有选中设备最新位置
|
||
180. **总览数据源** - 通过 `POST /api/locations/batch-latest` 获取所有设备最新位置 (含离线设备历史位置),非仅在线设备
|
||
181. **总览获取实时位置** - "获取实时位置" 按钮向所有在线设备广播 WHERE# 指令,回传后点"刷新位置"更新地图
|
||
182. **总览设备选择联动** - 勾选/取消设备立即刷新地图,全选/取消全选生效,取消勾选的设备从地图移除
|
||
183. **总览标记样式** - 标签显示 `名称 (IMEI后4位)`,边框颜色区分在线(绿)/离线(灰),填充色按定位类型(GPS蓝/WiFi青/LBS橙),弹窗显示名称+IMEI+在线状态+坐标+地址
|
||
|
||
### 广播指令改为全设备 (2026-03-31)
|
||
184. **broadcast API 改为全设备** - `POST /api/commands/broadcast` 从只查 online 改为查所有设备,逐一尝试 TCP 发送,未连接的跳过返回 "TCP not connected"
|
||
185. **前端提示更新** - 确认弹窗改为"向所有设备发送",结果提示"X 台已发送, Y 台未连接"
|
||
|
||
### 设备总览全面增强 (2026-03-31)
|
||
186. **设备面板交互分离** - 点击勾选框仅切换地图标记显示/隐藏,点击名称/IMEI 区域定位到设备并弹窗
|
||
187. **弹窗常驻模式** - 鼠标悬浮预览弹窗(移开消失),点击弹窗常驻(不消失),再次点击或关闭恢复悬浮模式;轨迹点/最新位置/总览标记统一行为
|
||
188. **设备搜索** - 总览左侧面板新增搜索框,实时过滤设备列表(匹配名称或IMEI)
|
||
189. **标记点缓存** - 首次加载后标记缓存在 `_ovMarkerMap`,勾选变更只做 setMap 显示/隐藏,不重新请求 API 和重建标记
|
||
190. **设备专属颜色** - 每台设备分配固定颜色(14色循环),标记点和轨迹线用同一色板,不同设备一眼区分
|
||
191. **多设备轨迹叠加** - "显示轨迹"加载所有勾选设备的当天轨迹,每台设备独立颜色折线+起终点标记
|
||
192. **轨迹线交互** - 悬浮加粗预览,点击高亮选中(加粗6px+不透明+zIndex置顶200),其他设备轨迹点隐藏,再次点击取消高亮
|
||
193. **三入口联动高亮** - 点击左侧设备名称/点击地图设备标记点/点击轨迹线均触发对应轨迹高亮,通过 `_ovDidToPL` 映射联动
|
||
194. **总览低精度过滤** - 独立的低精度按钮,切换时自动重新加载轨迹(跳过LBS点重连折线)
|
||
195. **离线设备支持** - batch-latest 查数据库历史位置(不限在线),取消勾选的设备点击名称仍可定位+查轨迹
|
||
|
||
## 待完成功能
|
||
|
||
### 第一优先级 — API 增强 (用于系统集成)
|
||
|
||
#### 第二批: 数据导出 API
|
||
5. **设备数据导出** - `GET /api/devices/export?format=csv` — 导出设备列表 CSV/Excel,支持状态/类型筛选
|
||
6. **位置数据导出** - `GET /api/locations/export?format=csv` — 导出位置记录 CSV,支持设备/时间/类型筛选
|
||
7. **告警数据导出** - `GET /api/alarms/export?format=csv` — 导出告警记录 CSV,支持类型/状态/时间筛选
|
||
8. **考勤数据导出** - `GET /api/attendance/export?format=csv` — 导出考勤记录 CSV,支持设备/来源/时间筛选
|
||
9. **蓝牙数据导出** - `GET /api/bluetooth/export?format=csv` — 导出蓝牙记录 CSV
|
||
10. **考勤报表导出** - `GET /api/attendance/report/export?format=csv` — 导出考勤日报表 (每设备每天汇总)
|
||
|
||
#### 第三批: 批量操作 + 筛选增强
|
||
11. **告警批量删除增强** - 支持按时间范围/类型/设备批量删除 (不限于选中 ID)
|
||
12. **位置记录清理增强** - `POST /api/locations/cleanup` — 删除N天前旧记录,支持按定位类型筛选
|
||
13. **设备分组管理** - 新增 DeviceGroup 模型,支持设备分组、按组查询、按组批量操作
|
||
14. **位置数据聚合** - `GET /api/locations/heatmap?device_id=&start_time=&end_time=` — 返回热力图数据 (经纬度+权重)
|
||
15. **告警规则配置** - 新增告警规则 API,支持自定义告警阈值 (低电量阈值、围栏停留时间等)
|
||
|
||
#### 第四批: 系统管理增强
|
||
16. **操作审计日志** - 新增 AuditLog 模型,记录所有 POST/PUT/DELETE 操作 (操作人/IP/端点/参数/时间)
|
||
17. **系统配置 API** - `GET/PUT /api/system/config` — 运行时配置查看和修改 (数据保留天数、清理间隔等)
|
||
18. **数据库备份 API** - `POST /api/system/backup` — 触发 SQLite 数据库备份,返回下载链接
|
||
19. **设备固件管理** - 新增固件版本记录 API,支持批量查询设备固件版本
|
||
|
||
### 第二优先级 — 前端与体验
|
||
|
||
20. **前端 WebSocket 集成** - admin.html Dashboard 改用 WebSocket 替代 30s 轮询,报警页实时通知弹窗
|
||
21. **考勤报表页面** - 前端新增考勤报表 Tab,调用 `GET /api/attendance/report` 展示每设备每天签到汇总
|
||
22. **心跳统计页面** - 数据日志页面心跳 Tab 增加统计面板,调用 `GET /api/heartbeats/stats` 展示设备活跃/异常/电量分布
|
||
23. **位置统计页面** - 位置追踪页面增加统计面板,调用 `GET /api/locations/stats` 展示定位类型分布/小时趋势
|
||
24. **导出按钮集成** - 各数据页面工具栏添加"导出 CSV"按钮 (需先完成第二批导出 API)
|
||
|
||
### 第三优先级 — 性能与架构
|
||
|
||
25. **性能优化第三批** - 迁移 PostgreSQL、多worker部署 (几千台设备时)
|
||
26. **API 版本化** - 添加 `/api/v1/` 前缀,为将来 v2 预留兼容空间
|
||
|
||
### 可选优化 (不影响功能)
|
||
27. **心跳扩展模块解析** - 计步器、外部电压等模块未解析 (已存原始 hex,按需解析)
|
||
28. **协议层深度统一** - tcp_server.py 辅助方法 (_parse_gps, _parse_datetime 等) 逐步迁移到 protocol/parser.py (代码重构)
|
||
|
||
### 已完成的 API 增强 (第一批 — 统计/聚合,2026-03-31)
|
||
- ✅ `GET /api/system/overview` — 系统总览 (设备在线率/今日统计/表记录数/DB大小)
|
||
- ✅ `GET /api/devices/stats` 增强 — 新增 by_type, battery_distribution, signal_distribution, online_rate
|
||
- ✅ `GET /api/locations/stats` — 位置统计 (总数/有坐标率/类型分布/小时分布)
|
||
- ✅ `GET /api/locations/track-summary/{id}` — 轨迹摘要 (距离/时长/速度/类型分布)
|
||
- ✅ `GET /api/alarms/stats` 增强 — 新增 today, by_source, daily_trend, top_devices
|
||
- ✅ `POST /api/alarms/batch-acknowledge` — 批量确认告警
|
||
- ✅ `GET /api/attendance/stats` 增强 — 新增 today, by_source, daily_trend, by_device
|
||
- ✅ `GET /api/attendance/report` — 考勤日报表 (每设备每天打卡次数/首末打卡/来源)
|
||
- ✅ `GET /api/bluetooth/stats` — 蓝牙统计 (类型分布/TOP信标/RSSI分布)
|
||
- ✅ `GET /api/heartbeats/stats` — 心跳统计 (活跃设备/电量/信号/间隔分析)
|
||
- ✅ `GET /api/fences/stats` — 围栏统计 (活跃数/绑定数/设备进出状态/今日事件)
|
||
- ✅ `GET /api/fences/{id}/events` — 围栏进出事件历史
|
||
- ✅ `GET /api/commands/stats` — 指令统计 (状态分布/类型分布/成功率/趋势)
|
||
- ✅ 前端 admin.html 同步: 仪表盘今日统计+在线率、告警批量确认、轨迹摘要、蓝牙/围栏/指令统计面板
|
||
|
||
## 调试技巧
|
||
|
||
```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/fences | python3 -m json.tool
|
||
curl -s http://localhost:8088/api/fences/all-active | python3 -m json.tool
|
||
curl -s -X POST http://localhost:8088/api/fences \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"name":"办公区","fence_type":"circle","center_lat":30.605,"center_lng":103.936,"radius":500}' | python3 -m json.tool
|
||
|
||
# POI 搜索 (服务端代理)
|
||
curl -s "http://localhost:8088/api/geocode/search?keyword=天府广场" | 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())
|
||
"
|
||
```
|