fix(app): 修复 AppController 安全与查询问题
- 加强异常处理、类型安全和图片上传校验 - 优化设备相关查询,避免重复访问数据源 - 补充并记录并发测试与审查修复实施计划
This commit is contained in:
@@ -38,14 +38,15 @@
|
||||
|
||||
| Topic 模式 | 正则 | 处理器 | 说明 |
|
||||
|------------|------|--------|------|
|
||||
| `/{identity}/publish/register` | `^/([^/]+)/publish/register$` | `DeviceRegisterHandler` | 设备注册/上线 |
|
||||
| `/{identity}/publish/power` | `^/([^/]+)/publish/power$` | `DeviceDataHandler` | 电量数据上报 |
|
||||
| `/{identity}/publish/register` | `^/([^/]+)/publish/register$` | `DeviceRegisterHandler` | 设备注册 |
|
||||
| `/{identity}/publish/status` | `^/([^/]+)/publish/status$` | `DeviceStatusHandler` | 设备 LWT 离线通知(仅处理 offline) |
|
||||
| `/{identity}/publish/power` | `^/([^/]+)/publish/power$` | `DeviceDataHandler` | 电量数据上报及在线心跳 |
|
||||
| `/{identity}/publish/finish/key` | `^/([^/]+)/publish/finish/key$` | `KeyFinishHandler` | 按键/手动浇水完成 |
|
||||
| `/{identity}/publish/finish/schedule` | `^/([^/]+)/publish/finish/schedule$` | `ScheduleFinishHandler` | 排程浇水完成 |
|
||||
| `/{identity}/publish/error` | `^/([^/]+)/publish/error$` | `ErromesHandler` | 设备异常告警 |
|
||||
| `/{identity}/publish/ack` | `^/([^/]+)/publish/ack$` | `DeviceCommandAckHandler` | 命令执行确认 |
|
||||
|
||||
> `{identity}` 可以是设备编号(deviceNo)或 MAC 地址,由 `DeviceIdentityResolver` 统一解析为 deviceNo。
|
||||
> 首次注册可使用 MAC 地址;注册完成后的业务 Topic 使用 deviceNo。LWT 离线 Topic 兼容 MAC 地址,服务端通过 `DeviceIdentityResolver` 解析为 deviceNo。
|
||||
|
||||
### 2.2 下行 Topic(服务端 → 设备)
|
||||
|
||||
@@ -304,8 +305,9 @@ long nextRetryAt; // 下次重试时间戳
|
||||
**服务端处理**:
|
||||
1. 通过 MAC 或设备编号解析入库设备
|
||||
2. 更新设备注册信息(名称、电量、固件版本等)
|
||||
3. 刷新设备在线状态到 Redis
|
||||
4. 自动回复设备编号(`registerDeviceNo` 命令)
|
||||
3. 自动回复设备编号(`registerDeviceNo` 命令)
|
||||
|
||||
> 注册成功即将设备标记为在线;设备取得 deviceNo 后仍必须定时上报电量,以持续刷新在线心跳。
|
||||
|
||||
---
|
||||
|
||||
@@ -317,11 +319,36 @@ long nextRetryAt; // 下次重试时间戳
|
||||
}
|
||||
```
|
||||
|
||||
**服务端处理**: 更新设备电量 + 刷新在线状态。
|
||||
**服务端处理**: 更新设备电量,将数据库设备状态改为在线,并将 Redis 在线心跳刷新为 600 秒。
|
||||
|
||||
设备必须周期上报,即使电量没有变化也要发送。建议每 5 分钟上报一次,连续 10 分钟未收到有效电量消息且期间无 ACK 刷新时,服务端将设备判定为离线。
|
||||
|
||||
---
|
||||
|
||||
### 5.3 按键浇水完成 — `/{identity}/publish/finish/key`
|
||||
### 5.3 设备离线遗嘱 — `/{identity}/publish/status`
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "offline"
|
||||
}
|
||||
```
|
||||
|
||||
**服务端处理**: 收到非 retained 的 `offline` 或 `0` 后立即将设备改为离线。`online` 或 `1` 不再用于上线,服务端会忽略,上线状态只由电量心跳维护。
|
||||
|
||||
设备应将该消息配置为 MQTT LWT,建议 `retain=false`。Topic 第一段优先使用 deviceNo;设备注册前无法取得 deviceNo 时可使用 MAC 地址。
|
||||
|
||||
兼容设备也可以发送以下格式,服务端会使用 `deviceMac` 解析设备,并将 `offline=true`(字符串或布尔值)按离线处理:
|
||||
|
||||
```json
|
||||
{
|
||||
"deviceMac": "AA:BB:CC:DD:EE:FF",
|
||||
"offline": "true"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 按键浇水完成 — `/{identity}/publish/finish/key`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -340,7 +367,7 @@ long nextRetryAt; // 下次重试时间戳
|
||||
|
||||
---
|
||||
|
||||
### 5.4 排程浇水完成 — `/{identity}/publish/finish/schedule`
|
||||
### 5.5 排程浇水完成 — `/{identity}/publish/finish/schedule`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -358,7 +385,7 @@ long nextRetryAt; // 下次重试时间戳
|
||||
|
||||
---
|
||||
|
||||
### 5.5 设备异常告警 — `/{identity}/publish/error`
|
||||
### 5.6 设备异常告警 — `/{identity}/publish/error`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -371,7 +398,7 @@ long nextRetryAt; // 下次重试时间戳
|
||||
|
||||
---
|
||||
|
||||
### 5.6 命令 ACK — `/{identity}/publish/ack`
|
||||
### 5.7 命令 ACK — `/{identity}/publish/ack`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -411,6 +438,7 @@ long nextRetryAt; // 下次重试时间戳
|
||||
| `maxRetryCount` | 3 | 最大重试次数 |
|
||||
| `retryIntervalMs` | 5000 | 重试间隔(ms) |
|
||||
| `scanIntervalMs` | 5000 | 定时扫描间隔(ms) |
|
||||
| `ackLockWaitMs` | 3000 | ACK 等待同一命令重试锁的最长时间(ms) |
|
||||
| `pendingTtlSeconds` | 86400 | pending 命令 TTL(秒) |
|
||||
| `ackTtlSeconds` | 86400 | ACK 结果缓存 TTL(秒) |
|
||||
|
||||
@@ -495,8 +523,14 @@ mqtt:
|
||||
pending-set-key: "mqtt:command:pending:ids"
|
||||
retry-lock-key-prefix: "lock:mqtt:command:retry:"
|
||||
retry-lock-ttl-ms: 30000
|
||||
ack-lock-wait-ms: 3000
|
||||
device-status-cache-prefix: "mqtt:device:status:"
|
||||
device-status-cache-ttl-seconds: 300
|
||||
device-status-cache-ttl-seconds: 600
|
||||
|
||||
offline-check:
|
||||
enabled: true
|
||||
ttl-compat-enabled: true
|
||||
interval-ms: 30000
|
||||
```
|
||||
|
||||
---
|
||||
@@ -538,10 +572,13 @@ public class MyNewHandler implements MqttTopicHandler {
|
||||
|
||||
## 10. 设备在线状态检测
|
||||
|
||||
设备在线状态通过 Redis 缓存管理:
|
||||
设备在线状态由电量上报和离线遗嘱共同管理:
|
||||
|
||||
- **写入时机**: 设备注册、电量上报、ACK 确认时刷新
|
||||
- **上线时机**: 注册成功或收到包含 `powerLevel` 的有效电量上报时刷新
|
||||
- **ACK 兼容逻辑**: 保留现有 ACK 在线刷新逻辑,收到有效 ACK 也会延长在线缓存
|
||||
- **缓存格式**: `{ "deviceNo": "01", "status": "1", "lastReportTime": "2026-06-25T14:30:00Z" }`
|
||||
- **TTL**: 默认 300 秒(5 分钟)
|
||||
- **离线判定**: 缓存过期 = 设备离线
|
||||
- **TTL**: 默认 600 秒(10 分钟)
|
||||
- **超时离线**: 每 30 秒扫描一次非离线设备,缓存过期后更新数据库状态为离线
|
||||
- **遗嘱离线**: 收到非 retained 的 LWT `status=offline/0` 时立即离线
|
||||
- **忽略在线状态**: `status=online/1` 不会刷新在线状态
|
||||
- **并发保护**: 使用分布式锁(`lock:mqtt:device:status:{deviceNo}`)防止并发写入
|
||||
|
||||
Reference in New Issue
Block a user