固件与 OTA 服务
固件管理、存储代理和 OTA 升级任务调度的后端模块设计。整体架构见 11.OTA升级架构,MQTT Topic 见 06.MQTT通信协议 3.3 节。
1. 模块定位
两个子模块,归属于 NestJS 后端:
| 模块 | 职责 | 对应数据库表 |
|---|---|---|
| FirmwareModule | 固件上传、版本管理、OSS 存储代理 | firmwares |
| OtaModule | 升级任务创建、调度、进度追踪、结果记录 | ota_tasks |
2. FirmwareModule:固件管理
2.1 固件上传流程
FirmwareService.upload(file, productId, version, changelog) 流程:
- Web 后台通过
POST /api/v1/firmware/upload提交.bin文件 - 校验文件格式(见 2.2 节)
- 计算文件 MD5 和大小
- 通过
StorageService上传到阿里云 OSS,存储路径firmware/{productKey}/{version}/{filename}.bin - 写入
firmwares表(productId、version、url、size、md5、changelog) - 返回固件版本信息给前端
2.2 文件校验规则
| 校验项 | 规则 | 错误码 |
|---|---|---|
| 扩展名 | 必须是 .bin | A0301 |
| 魔数 | 文件头第一个字节为 0xE9(ESP32 固件标识) | A0301 |
| 版本号 | 同产品下 version 唯一,重复拒绝 | A0302 |
| 文件大小 | 最大 4MB(ESP32 典型 OTA 分区大小) | A0001 |
2.3 StorageService 抽象
定义统一的文件存储接口,适配 OSS 和 MinIO:
| 方法 | 说明 |
|---|---|
upload(file, path) | 上传文件,返回文件 URL |
getSignedUrl(path, ttl) | 生成预签名下载链接(设备端用,默认 24h TTL) |
delete(path) | 删除固件文件 |
使用 ali-oss SDK,Bucket 名称和 AccessKey 通过环境变量注入。
2.4 目录结构
src/modules/firmware/
├── firmware.module.ts
├── firmware.service.ts # 上传、校验、版本查询
├── firmware.controller.ts # REST API
└── storage/ # 存储抽象层
├── storage.interface.ts
└── oss.storage.ts3. OtaModule:升级任务
3.1 任务创建与下发
OtaService.createTask(firmwareId, deviceIds) 流程:
- 查询 Firmware 记录,确认固件存在且 URL 有效
- 查询目标设备列表,过滤离线设备(标记为
pending,待上线后补发) - 为每台设备创建 OtaTask 记录(status=
pending) - 通过
MqttService.publish()逐批下发通知,每批 100 台,间隔 30 秒 - 下发后将任务状态更新为
notified - 返回任务 ID 和总设备数给 Web 后台
3.2 进度追踪
OtaService.updateProgress(deviceId, progress, desc) 流程:
- 查找该设备当前进行中的 OtaTask(status=
notified或downloading) - 更新
progress字段 - 根据
desc转换状态:
| desc 值 | status 变更 | 说明 |
|---|---|---|
downloading | 保持为 downloading | 更新进度百分比 |
flashing | → installing | 下载完成,正在写入分区 |
done | → success | 写入完成,记录 completedAt |
verify_failed | → failed(或自动重试) | 校验失败,检查重试次数 |
- 通过 WebSocket 推送最新进度到 Web 后台(见 10.WebSocket实时通信)
3.3 离线设备补发机制
设备首次属性上报时检查 pending OTA 任务:
OtaService查询该设备是否有pending或notified状态的 OtaTask- 有则通过 MQTT 立即补发升级通知
- 补发前重新生成预签名 URL(确保下载链接未过期)
3.4 重试策略
| 场景 | 策略 |
|---|---|
| MD5 校验失败 | 自动重新创建任务,最多重试 2 次 |
| 每次重试 | 重新生成预签名 URL(旧 URL 可能已过期) |
| 2 次重试仍失败 | 标记 failed,WebSocket 通知运维 |
3.5 批量升级并发控制
| 参数 | 值 | 说明 |
|---|---|---|
| 单次任务最大设备数 | 500 台 | 防止 MQTT 消息风暴 |
| 分批间隔 | 30 秒 | 逐批下发,给 OSS 带宽缓冲 |
| 预签名 URL TTL | 24 小时 | 确保离线设备上线后能下载 |
3.6 目录结构
src/modules/ota/
├── ota.module.ts
├── ota.service.ts # 任务创建、调度、进度追踪
├── ota.controller.ts # REST API
└── ota.gateway.ts # WebSocket 进度推送4. 与 MQTT 路由的集成
OtaModule 注册到 MQTT 路由系统(04.MQTT服务设计 第 2.3 节),通过 ota.handler.ts 处理 OTA 相关 Topic:
| MQTT Topic | Handler | 调用 |
|---|---|---|
sys/+/+/thing/ota/progress | ota.handler.ts | OtaService.updateProgress() |
FirmwareModule 不直接参与 MQTT 通信,仅通过 REST API 供 Web 后台调用。OTA 通知下发通过 MqttService.publish() 完成。
5. 预签名 URL 生成
设备通过预签名 URL 下载固件,避免直接暴露 OSS Bucket:
| 机制 | 说明 |
|---|---|
| 下载 URL 有效期 | 24 小时,超时后 OSS 拒绝访问 |
| 离线设备补发 | 上线时检测到未完成的 OtaTask,重新生成 URL 后补发 |
| API 封装 | 使用 ali-oss SDK 的 signatureUrl() 方法生成预签名 URL |
设备端只需发送 HTTP GET 请求到预签名 URL 即可下载,无需携带额外认证信息。
