chore(repo): 导入 M-Z 业务底座 V1.9 基线并建立仓库规范
按周工交付的 SEC-Z-01 清理后源码建立 M-Z 主干,并按 Git 仓库惯例重排目录。 - aomin-gateway/ 后端工程 87 文件,原样保留,路径与构建命令未变 - demo/ 演示前端与设备模拟器 9 文件(原 02_前端与设备端),非正式三端 - docs/handover/ 交付与安全清理材料 6 份,内容未改 - docs/contract/ J-00 规格、OpenAPI、内部五接口、状态机、Mock 样例 - docs/reference/ 指纹与校验清单,含 84 个源码文件的 sha256 清单 目录重排只动位置不动内容:源码树 84 文件哈希与交付一致,逐文件自校验通过; 12 条 Flyway 迁移与交付包内镜像逐字节一致。 未纳入:迁移镜像目录(与工程内一套重复,改留指纹)、target/、证据 raw、 应急恢复脚本、评审用虚构台账数据。 安全:SEC-Z-01 15 处明文已清零,导入前重新扫描无新增命中,本仓库无历史污染。 行尾按交付字节原样保存(.gitattributes 设 * -text),不做归一化以免整树摘要失效。
This commit is contained in:
commit
a7605ef8b8
121 files changed
+23629
No files matched your search
@@ -0,0 +1,970 @@
|
||||
openapi: 3.0.3
|
||||
|
||||
info:
|
||||
title: 鹤仆云 · IoT 接入接口(internal)
|
||||
version: 1.8.1
|
||||
description: |
|
||||
本文件是 `J-00 接入接口规格 V1.7` 的机器可执行版。
|
||||
|
||||
**两者关系(不要理解成两份契约):**
|
||||
- **本文件管「长什么样」**:路径、方法、字段类型、必填、枚举、错误码——工具可执行、可校验、可生成代码。
|
||||
- **J-00 管「为什么这样」**:业务语义、告警状态机、四条硬边界、幂等三档、断档与 0x04。这些写不进 YAML。
|
||||
- 二者不一致时,以本文件为准,同时回改 J-00;但**业务语义的解释权仍在 J-00**。
|
||||
|
||||
**V1.8.1 变更(2026-11-12):**
|
||||
- `CommandResult.claimGeneration` 进 `required`、取消 `nullable`;
|
||||
`nodeId` 补必填语义(与 I-18 落库的 `claimed_node_id` 比对,不符按 `NODE_MISMATCH` 拒)。
|
||||
- `RejectReason` 补命令侧四个原因码。
|
||||
|
||||
**V1.7.2 变更(2026-11-11):**
|
||||
- `I-18` 响应新增 `claimGeneration`(领取代次),`I-05` 新增同名字段用于回带。
|
||||
用途:一条命令租约到期被回收重派后,第一个节点的**迟到回执**会覆盖第二次的流转且不报错。
|
||||
回带代次后服务端能用 `WHERE claim_generation = ?` 挡住它(更新 0 行、计 `duplicated`)。
|
||||
- **兼容性**:`I-18` 必返;`I-05` 不回带也不会被拒,但等于没有代次校验。**V1.8 起 `I-05` 必填。**
|
||||
|
||||
**V1.7.1 变更(2026-11-10,需接入段确认后生效):**
|
||||
- `Event.rawRef` 由**可选改必填**,并取消「墓碑帧为 `null`」的例外。
|
||||
墓碑帧同样是独立的一条 I-01 事件(`eventType=MSG_UNDELIVERED`),有自己的原始字节,
|
||||
摘要填**承载该 `undelivered[]` 的网关上行帧**,多对一共享同一 `rawRef` 是合法的。
|
||||
墓碑行身份是 `sensorId + seq`,事件幂等键是 `eventId`,都不依赖 `rawRef`,去重逻辑不受影响。
|
||||
V1.7.1 之前已入库的历史墓碑行保持 `null`,业务层兼容。
|
||||
|
||||
**V1.7 变更(对齐《总体架构与 S 线一期实施规范 V1.0 定稿版》):**
|
||||
1. `protocolVersion` 不再接受 `tcp-1.10`(旧口径已废止)。
|
||||
2. `seq` 不再是「仅 G 线」——铠湾序列号、bignbiot 帧号等分别映射;没有才传 `null`。
|
||||
3. `rawRef` 允许 **MD5(32 位)或 SHA-256(64 位)**,但 **I-01 与 I-12 必须同一算法**。
|
||||
4. `eventType` 枚举外的新值 → 映射 `UNKNOWN` 并填 `payload.rawEventCode`,不静默丢弃。
|
||||
5. `resultCode` 改为**标准字典值**(`0/1/2/3/4`),厂家原码进新增的可选字段 `rawResultCode`。
|
||||
6. 命令状态七态英文(`PENDING/CLAIMED/SENT/ACKED/FAILED/TIMEOUT/CANCELLED`),
|
||||
**领取后是 `CLAIMED`,不是「已下发」**。
|
||||
|
||||
**五条不可协商的约束(J-00 §0.1 / §五):**
|
||||
1. 全部接口由 **IoT 侧发起**,业务层一个出站请求都不发。
|
||||
2. IoT 侧**不持有数据库账号**,不直连库。
|
||||
3. IoT 侧**不判设备是否建档**——未建档设备照常上报,由业务层分流。
|
||||
4. IoT 侧**不判业务策略**,只报亲眼看见的事实。
|
||||
5. `deviceTime` 没有就传 `null`,**禁止用 `receivedAt` 顶替**。
|
||||
|
||||
**编号台账(J-00 §1.1):** 在役 `I-01`/`I-02`/`I-05`/`I-12`/`I-18`;二期预留 `I-17`。
|
||||
废止永不复用:`I-03`、`I-04`、`I-06`~`I-11`、`I-13`~`I-16`。
|
||||
|
||||
servers:
|
||||
- url: https://{host}/internal/iot/v1
|
||||
description: 内网地址,由部署时确定。**不走公网、不做域名备案**。
|
||||
variables:
|
||||
host:
|
||||
default: 127.0.0.1:8080
|
||||
description: 业务层内网入口
|
||||
|
||||
tags:
|
||||
- name: 上行
|
||||
description: IoT → 业务层,推送设备数据
|
||||
- name: 下行
|
||||
description: IoT → 业务层,领取命令与回报结果
|
||||
- name: 二期预留
|
||||
description: 本期不实现
|
||||
|
||||
security:
|
||||
- mtls: []
|
||||
internalToken: []
|
||||
|
||||
paths:
|
||||
|
||||
/events/batch:
|
||||
post:
|
||||
tags: [上行]
|
||||
operationId: ingestEvents
|
||||
summary: I-01 设备事件批量上报
|
||||
description: |
|
||||
上报「能变成业务事件」的帧。心跳、鉴权帧、解析失败的未知帧**不从这里进**,走 I-12。
|
||||
|
||||
**限流**:单实例 200 条/批,1000 条/秒。
|
||||
**幂等键**:`{sourceChannel}-{批内首条 eventId}-{条数}`。重推同一批必须算出同一个键。
|
||||
**单条失败不整批回滚**:200 条里 1 条非法,其余 199 条照常入库。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- $ref: '#/components/parameters/Sign'
|
||||
- $ref: '#/components/parameters/Timestamp'
|
||||
- $ref: '#/components/parameters/Nonce'
|
||||
- $ref: '#/components/parameters/IdempotencyKeyEvent'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EventBatchRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 受理完成(含部分失败)。**HTTP 200 只表示受理,不表示处理完**。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngestResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
|
||||
/device-status/batch:
|
||||
post:
|
||||
tags: [上行]
|
||||
operationId: ingestDeviceStatus
|
||||
summary: I-02 设备状态快照批量上报
|
||||
description: |
|
||||
并发上报「在线/电量/CSQ/最后报文时刻」。业务层按 `sensorId` **覆盖写**(谁最新谁覆盖),
|
||||
**不做时间排序**,也**不参与告警判定**——只作界面展示与离线判断的原料。
|
||||
|
||||
**限流**:500 条/批。
|
||||
**幂等键**:`{sourceChannel}-{批内首条 sensorId}-{reportedAt}-{条数}`。
|
||||
⚠️ 注意:**I-02 报文里没有 `eventId`**,不能用它算幂等键。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- $ref: '#/components/parameters/Sign'
|
||||
- $ref: '#/components/parameters/Timestamp'
|
||||
- $ref: '#/components/parameters/Nonce'
|
||||
- $ref: '#/components/parameters/IdempotencyKeyStatus'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StatusBatchRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 受理完成
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngestResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
|
||||
/raw-frames:
|
||||
post:
|
||||
tags: [上行]
|
||||
operationId: ingestRawFrames
|
||||
summary: I-12 原始报文批量上报
|
||||
description: |
|
||||
**全集的口子**——所有上行帧都从这里进一次,其中「能变成业务事件」的那部分另由 I-01 再报一次。
|
||||
同一帧走两条链路不是重复:一条存档,一条产生业务后果。
|
||||
|
||||
**限流**:200 条/批。
|
||||
**幂等键**:`{sourceChannel}-{批内首条 rawRef}-{条数}`。
|
||||
⚠️ 注意:**I-12 报文里没有 `eventId`**;`rawRef` 就是 `MD5(原始帧字节)`,重推同一帧必然算出同一个值。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- $ref: '#/components/parameters/Sign'
|
||||
- $ref: '#/components/parameters/Timestamp'
|
||||
- $ref: '#/components/parameters/Nonce'
|
||||
- $ref: '#/components/parameters/IdempotencyKeyRaw'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RawFrameBatchRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 受理完成
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngestResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
|
||||
/commands/pending:
|
||||
get:
|
||||
tags: [下行]
|
||||
operationId: pullPendingCommands
|
||||
summary: I-18 长轮询领取命令
|
||||
description: |
|
||||
⚠️ **本接口编号是 I-18**(原 `I-04` 已废止、永不复用)。老文档里的 `I-04` 不要再实现。
|
||||
|
||||
**长轮询**:`wait` 是最长挂起秒数,不是轮询间隔。有新命令立即返回,不等满。
|
||||
服务端用 `DeferredResult` 挂起,**不占线程**。
|
||||
|
||||
**`gatewayIds` 只传当前在线的接入端点**——离线端点的命令因此一条都不会被拉走,
|
||||
天然实现「等上线再发」。IoT 不需要知道这个 id 代表网关还是单品。
|
||||
|
||||
**租约**:拉走不等于拿走。业务层返回时置 **`CLAIMED`**、`leaseExpireAt = now + 60s`;
|
||||
租约内不回报会自动回 `PENDING`,`reclaimCount + 1`。
|
||||
|
||||
⚠️ **领取这一步不能写「已下发」**——命令还在 IoT 手里没发出去。`CLAIMED`(领走)与
|
||||
`SENT`(已发出)是两个状态,混成一个值,租约回收就判不出该不该重派。
|
||||
|
||||
**签名注意**:本接口是 GET,无 body —— `X-Sign` 里的 `sha256(body)` 取**空串的 hash**;
|
||||
参与签名的 `path` **含 query**,`?nodeId=…&wait=30` 一并算进去。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- $ref: '#/components/parameters/Sign'
|
||||
- $ref: '#/components/parameters/Timestamp'
|
||||
- $ref: '#/components/parameters/Nonce'
|
||||
- name: nodeId
|
||||
in: query
|
||||
required: true
|
||||
description: IoT 实例标识。多实例时业务层按它记租约。
|
||||
schema:
|
||||
type: string
|
||||
maxLength: 64
|
||||
- name: gatewayIds
|
||||
in: query
|
||||
required: true
|
||||
description: |
|
||||
该实例**当前在线**的接入端点,逗号分隔,单次 ≤100。
|
||||
**离线端点不要传**——传了等于让业务层以为能发。
|
||||
schema:
|
||||
type: string
|
||||
example: OWN_GW_A,OWN_GW_B
|
||||
- name: wait
|
||||
in: query
|
||||
required: false
|
||||
description: 长轮询最长挂起秒数。0 = 立即返回。默认 30,上限 60。
|
||||
schema:
|
||||
type: integer
|
||||
default: 30
|
||||
minimum: 0
|
||||
maximum: 60
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
description: 单次最多返回条数。默认 50,上限 100。
|
||||
schema:
|
||||
type: integer
|
||||
default: 50
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
responses:
|
||||
'200':
|
||||
description: 返回命令列表。无命令时 `commands` 为空数组(长轮询到期的情况)。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PendingResponse'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
|
||||
/commands/result:
|
||||
post:
|
||||
tags: [下行]
|
||||
operationId: reportCommandResults
|
||||
summary: I-05 命令结果批量回报
|
||||
description: |
|
||||
回报命令执行结果。**`resultCode` 是标准字典值(`0` 成功/`1` 不支持/`2` 参数非法/
|
||||
`3` 执行失败/`4` 设备忙),不是厂家原码**——厂家原始返回码请放 `rawResultCode`。
|
||||
把原码直透到标准字段,业务层与前端就得按每个厂家各写一套判断。
|
||||
|
||||
**幂等键**:`{nodeId}-{批内首条 commandId}-{条数}`。
|
||||
|
||||
**五个 `result` 值的含义(不要合并):**
|
||||
- `ACKED` 设备有回应,看 `resultCode` 分流
|
||||
- `SENT` 已发出,本协议无回执通道(S 线裸 TCP)——业务层置 `SENT` + 24h 超时兜底
|
||||
- `TIMEOUT` 已发出,等不到回执
|
||||
- `DEVICE_OFFLINE` **没有发出去**(连接不在)
|
||||
- `FAILED` IoT 自身发送失败(编码/写通道异常)
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InternalToken'
|
||||
- $ref: '#/components/parameters/Sign'
|
||||
- $ref: '#/components/parameters/Timestamp'
|
||||
- $ref: '#/components/parameters/Nonce'
|
||||
- $ref: '#/components/parameters/IdempotencyKeyResult'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ResultBatchRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 受理完成
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngestResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
|
||||
/commands/{commandId}/dump:
|
||||
post:
|
||||
tags: [二期预留]
|
||||
operationId: uploadDiagDump
|
||||
summary: 【二期预留,本期不实现】I-17 诊断包分片上传
|
||||
description: |
|
||||
**本期零调用,不要实现。** 一期 `diag_dump` 只留命令字,实际不下发。
|
||||
等 `set_ota` 与诊断包真正启用时再补。
|
||||
|
||||
编号说明:原 `I-13` 在系统设计 V1.4 曾指「网关台账快照」,该功能取消后号一并作废,
|
||||
诊断包另起 `I-17`,不沿用旧号。
|
||||
parameters:
|
||||
- name: commandId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
maxLength: 64
|
||||
responses:
|
||||
'404':
|
||||
description: |
|
||||
本期不实现:**这条路由没有注册**。
|
||||
请求由 Spring 默认错误处理返回 404,`body` 是框架的错误结构,
|
||||
**不是本文档其他接口那种 `{code,msg,data}` 统一信封**,不要按信封解析。
|
||||
留这一条只是占住 `I-17` 这个号,等二期真做时再补正式响应定义。
|
||||
|
||||
components:
|
||||
|
||||
securitySchemes:
|
||||
mtls:
|
||||
type: mutualTLS
|
||||
description: 双向证书,90 天轮换。**没有它,其余四层都不必看**。
|
||||
internalToken:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-Internal-Token
|
||||
description: 固定长随机串,配置中心下发。
|
||||
|
||||
parameters:
|
||||
InternalToken:
|
||||
name: X-Internal-Token
|
||||
in: header
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
Sign:
|
||||
name: X-Sign
|
||||
in: header
|
||||
required: true
|
||||
description: |
|
||||
`HMAC-SHA256(token, method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body))`
|
||||
|
||||
- `path` **含 query**(GET 时尤其注意)
|
||||
- GET 无 body 时,`sha256(body)` 取**空串的 hash** `e3b0c442…b855`,不要取字面量 `null`
|
||||
schema:
|
||||
type: string
|
||||
Timestamp:
|
||||
name: X-Timestamp
|
||||
in: header
|
||||
required: true
|
||||
description: Unix 秒。与服务器偏差 > 300 秒直接拒。
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
Nonce:
|
||||
name: X-Nonce
|
||||
in: header
|
||||
required: true
|
||||
description: 随机串。Redis 留 600 秒做防重放。
|
||||
schema:
|
||||
type: string
|
||||
maxLength: 64
|
||||
IdempotencyKeyEvent:
|
||||
name: X-Idempotency-Key
|
||||
in: header
|
||||
required: true
|
||||
description: '`{sourceChannel}-{批内首条 eventId}-{条数}`'
|
||||
schema:
|
||||
type: string
|
||||
example: OWN_GW-3F7A9C21_10231-2
|
||||
IdempotencyKeyStatus:
|
||||
name: X-Idempotency-Key
|
||||
in: header
|
||||
required: true
|
||||
description: '`{sourceChannel}-{批内首条 sensorId}-{reportedAt}-{条数}`。**不要用 eventId,I-02 没有这个字段**'
|
||||
schema:
|
||||
type: string
|
||||
example: HORN_CELL-868123456789012-2026-10-29T10:19:22+08:00-500
|
||||
IdempotencyKeyRaw:
|
||||
name: X-Idempotency-Key
|
||||
in: header
|
||||
required: true
|
||||
description: '`{sourceChannel}-{批内首条 rawRef}-{条数}`。**不要用 eventId,I-12 没有这个字段**'
|
||||
schema:
|
||||
type: string
|
||||
example: MIR_CELL-9f2c1e7b8a4d6f03c5e9a1b7d4f8c2e6-200
|
||||
IdempotencyKeyResult:
|
||||
name: X-Idempotency-Key
|
||||
in: header
|
||||
required: true
|
||||
description: '`{nodeId}-{批内首条 commandId}-{条数}`'
|
||||
schema:
|
||||
type: string
|
||||
example: iot-node-1-CMD-20261030-000123-50
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: 请求体不符合本文件定义。**不重试**,字段问题重试也不会变。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
Unauthorized:
|
||||
description: mTLS 失败、token 错、签名不匹配、时间戳超窗、nonce 重放。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
TooManyRequests:
|
||||
description: 超出限流。**不重试**(重试还是被限),计入死信即可。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
ServerError:
|
||||
description: 服务端异常。**这是唯一需要重试的**,按 J-00 §1.6 的五档退避。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
schemas:
|
||||
|
||||
SourceChannel:
|
||||
type: string
|
||||
description: |
|
||||
接入通道四值,**不是产品线**。同一户可以存在多个通道的设备。
|
||||
它决定三件事:`dev_id` 生成规则、`ts_precision` 默认值、原文落哪张表。
|
||||
|
||||
⚠️ **它是业务来源,不承担具体物理通路。** 哪套协议、哪个监听口进来,由接入侧的
|
||||
`linkType` 描述(落 `adapter_in_raw`,不进本信封),两者分列。
|
||||
例:S 线两套 TCP 同属 `MIR_CELL`,靠 `linkType` 区分,不能靠这个字段猜协议。
|
||||
enum: [OWN_GW, HORN_HOST, HORN_CELL, MIR_CELL]
|
||||
|
||||
TsSource:
|
||||
type: integer
|
||||
description: |
|
||||
时源,**没有默认值,漏传直接拒收**(故意不设 default,让漏赋值当场暴露)。
|
||||
`0` 设备原始 / `1` 厂家云 / `2` 平台接收 / `3` 网关打戳。
|
||||
enum: [0, 1, 2, 3]
|
||||
|
||||
EventType:
|
||||
type: string
|
||||
description: |
|
||||
29 个值,按家族成对出现。**少一个「解除」,告警就永远不闭环。**
|
||||
枚举外的新值**映射 `UNKNOWN` 并必填 `payload.rawEventCode`**——不拒收、不静默丢弃。
|
||||
enum:
|
||||
# 成对:发生 / 解除
|
||||
- DOOR_OPEN
|
||||
- DOOR_CLOSE
|
||||
- WATER_ACTIVITY
|
||||
- WATER_ACTIVITY_STOP
|
||||
- CURRENT_ACTIVITY
|
||||
- CURRENT_ACTIVITY_STOP
|
||||
- WATER_LEAK
|
||||
- WATER_LEAK_CLEAR
|
||||
- GAS_LEAK
|
||||
- GAS_LEAK_CLEAR
|
||||
- SMOKE
|
||||
- SMOKE_CLEAR
|
||||
- FALL
|
||||
- FALL_CLEAR
|
||||
- SOS
|
||||
- SOS_CLEAR
|
||||
- TAMPER
|
||||
- TAMPER_CLEAR
|
||||
- POWER_CUT
|
||||
- POWER_RESTORE
|
||||
# 单值
|
||||
- VALVE_CLOSED
|
||||
- TEMP_HUMIDITY
|
||||
- DEVICE_ONLINE
|
||||
- DEVICE_OFFLINE
|
||||
- LOW_BATTERY
|
||||
- HEARTBEAT
|
||||
- PRE_ALARM_0X04
|
||||
- MSG_UNDELIVERED
|
||||
- UNKNOWN
|
||||
|
||||
EventPayload:
|
||||
type: object
|
||||
description: 事件明细。已知字段逐个列;**厂商新增字段原样保留,不丢**(见 `additionalProperties`)。
|
||||
required: [deviceTypeRaw]
|
||||
properties:
|
||||
deviceTypeRaw:
|
||||
type: integer
|
||||
description: |
|
||||
设备原始类型码(G 线 `type` 字节、S 线 `DT`、H 线子设备类型)。
|
||||
**任何帧都带,含心跳与 UNKNOWN。** 不要在这里做归一化——归一化由业务层按设备类型表做。
|
||||
rawEventCode:
|
||||
type: integer
|
||||
description: 设备原始事件码。**`eventType` 落到 `UNKNOWN` 时必填**,其余情况建议带。
|
||||
batteryMv:
|
||||
type: integer
|
||||
description: 电池电压,**毫伏整数**。不换算成百分比(换算式会变,在业务层)。
|
||||
rssi:
|
||||
type: integer
|
||||
description: 信号强度 dBm。
|
||||
csq:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 99
|
||||
description: 信号质量 0–31,`99` = 未知。
|
||||
additionalProperties: true
|
||||
|
||||
Event:
|
||||
type: object
|
||||
required:
|
||||
- eventId
|
||||
- sensorId
|
||||
- sourceChannel
|
||||
- eventType
|
||||
- receivedAt
|
||||
- tsSource
|
||||
- protocolVersion
|
||||
- rawRef
|
||||
- payload
|
||||
properties:
|
||||
eventId:
|
||||
type: string
|
||||
maxLength: 96
|
||||
description: |
|
||||
事件级幂等键,**≤96 字符,不得含平台时间戳**。
|
||||
算法见 J-00 §2.4 三档:有 `seq` 用 `{sensorId}_{seq}`;有厂商报文 ID 用它;
|
||||
两者都没有用 `{sensorId}_{sha256(原始帧字节)前16位}`。
|
||||
**hash 要对原始字节算**,不能对反序列化后再序列化的 JSON 算。
|
||||
example: 3F7A9C21_10231
|
||||
sensorId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
description: |
|
||||
设备物理标识,**必须与 `device.sensor_id` 完全一致(含厂商前缀)**。
|
||||
H/S 线必带 `HORN:`/`MIR:` 前缀;G 线 8 位 hex 不加前缀。
|
||||
**这是全篇最容易出错的一个字段**——不一致会导致全场设备判成未建档。
|
||||
example: 3F7A9C21
|
||||
sourceChannel:
|
||||
$ref: '#/components/schemas/SourceChannel'
|
||||
eventType:
|
||||
$ref: '#/components/schemas/EventType'
|
||||
deviceTime:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: |
|
||||
设备侧时间,**没有就传 `null`**。
|
||||
⚠️ **禁止用 `receivedAt` 顶替**——填了等于伪造时源,界面会把平台收到的时刻当事情发生的时刻。
|
||||
receivedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 平台接收时刻,ISO-8601 带时区。
|
||||
example: '2026-10-29T10:19:22+08:00'
|
||||
tsSource:
|
||||
$ref: '#/components/schemas/TsSource'
|
||||
protocolVersion:
|
||||
type: string
|
||||
maxLength: 16
|
||||
description: |
|
||||
按**具体协议原件**填写,厂商改协议后要能回溯。
|
||||
例:`gw-1.2`(契约 B)/`WTK-V3.0`(铠湾)/`bignbiot-3.0`/`nb-1.6`(NB&WIFI)。
|
||||
**豪恩不得再写 `tcp-1.10`**——旧口径已废止。
|
||||
example: gw-1.2
|
||||
seq:
|
||||
type: integer
|
||||
format: int64
|
||||
nullable: true
|
||||
description: |
|
||||
协议序号。**G 线**取 433 帧 `count`(2 字节);**铠湾 V3.0** 取帧内序列号;
|
||||
**bignbiot** 取帧号(1 字节 1–255);其他协议取自带序号。
|
||||
**没有才传 `null`,不得写成「S 线一律 null」**——S 线两套 TCP 都有序号。
|
||||
payload:
|
||||
$ref: '#/components/schemas/EventPayload'
|
||||
rawRef:
|
||||
type: string
|
||||
pattern: '^([a-f0-9]{32}|[a-f0-9]{64})$'
|
||||
description: |
|
||||
原始报文摘要,回指 I-12 的 `rawRef`。
|
||||
**MD5(32 位小写 hex)与 SHA-256(64 位小写 hex)都接受**,按长度识别。
|
||||
**约束只有一条:I-01 与 I-12 用同一种算法**,否则回指链断开。
|
||||
摘要只用于关联与幂等,不作鉴权。
|
||||
|
||||
**必填(V1.7.1 起)。墓碑帧也必填**:填承载该 `undelivered[]` 的网关上行帧摘要。
|
||||
一条网关报文里的 N 个 `undelivered` → N 条墓碑行共享同一 `rawRef`,多对一合法。
|
||||
墓碑行身份是 `sensorId + seq`、事件幂等键是 `eventId`,都**不是** `rawRef`,故不影响去重。
|
||||
V1.7.1 之前已入库的历史墓碑行为 `null`,业务层保持兼容。
|
||||
example: 9f2c1e7b8a4d6f03c5e9a1b7d4f8c2e6
|
||||
|
||||
EventBatchRequest:
|
||||
type: object
|
||||
required: [sourceChannel, events]
|
||||
properties:
|
||||
sourceChannel:
|
||||
$ref: '#/components/schemas/SourceChannel'
|
||||
events:
|
||||
type: array
|
||||
maxItems: 200
|
||||
items:
|
||||
$ref: '#/components/schemas/Event'
|
||||
|
||||
DeviceStatus:
|
||||
type: object
|
||||
required: [sensorId, sourceChannel, online, lastSeenAt, protocolVersion, reportedAt]
|
||||
properties:
|
||||
sensorId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
sourceChannel:
|
||||
$ref: '#/components/schemas/SourceChannel'
|
||||
online:
|
||||
type: boolean
|
||||
description: |
|
||||
只表示「最近是否有报文」,**不表示设备好坏**。
|
||||
是否「离线告警」由业务层按阈值判定,IoT 不判。
|
||||
lastSeenAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 最后一次收到有效报文的时刻。
|
||||
batteryMv:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: 毫伏整数。
|
||||
csq:
|
||||
type: integer
|
||||
nullable: true
|
||||
minimum: 0
|
||||
maximum: 99
|
||||
lastSeq:
|
||||
type: integer
|
||||
format: int64
|
||||
nullable: true
|
||||
description: 仅 G 线,最后收到的 433 序号。
|
||||
lostCnt:
|
||||
type: integer
|
||||
nullable: true
|
||||
minimum: 0
|
||||
maximum: 255
|
||||
description: 仅 G 线,窗口内未送达计数,`255` 表示饱和。
|
||||
protocolVersion:
|
||||
type: string
|
||||
maxLength: 16
|
||||
reportedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 快照生成时刻。**幂等键要用它**。
|
||||
|
||||
StatusBatchRequest:
|
||||
type: object
|
||||
required: [sourceChannel, statuses]
|
||||
properties:
|
||||
sourceChannel:
|
||||
$ref: '#/components/schemas/SourceChannel'
|
||||
statuses:
|
||||
type: array
|
||||
maxItems: 500
|
||||
items:
|
||||
$ref: '#/components/schemas/DeviceStatus'
|
||||
|
||||
RawFrame:
|
||||
type: object
|
||||
required: [rawRef, sourceChannel, receivedAt, rawFrame, protocolVersion]
|
||||
properties:
|
||||
rawRef:
|
||||
type: string
|
||||
pattern: '^[a-f0-9]{32}$'
|
||||
description: '`MD5(原始帧字节)`,32 位小写 hex。**它就是幂等键**,也是 I-01 里 `rawRef` 回指的目标。'
|
||||
sensorId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
nullable: true
|
||||
description: |
|
||||
解析出设备标识时带上,规则同 I-01。**未建档设备也要带**——有值才追得回是哪个 SN。
|
||||
实在解析不出可留空,原文照样落库。
|
||||
sourceChannel:
|
||||
$ref: '#/components/schemas/SourceChannel'
|
||||
receivedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 平台收到这一帧的时刻。**不是设备时间。**
|
||||
rawFrame:
|
||||
type: string
|
||||
format: byte
|
||||
description: |
|
||||
**Base64,定死这一种表示。**
|
||||
十六进制会把体积翻倍,而 200 条/批本来就是按字节数撑上限。
|
||||
`rawRef` 对解码后的字节算——两种表示算出来一样,所以「写 hex 或 Base64 等于没定」。
|
||||
写 hex 的实现方,MD5 永远对不上业务层。
|
||||
frameBytes:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: 解码后的字节数。用于自检——**对不上说明 Base64 编码有问题**。
|
||||
gatewayDevId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
nullable: true
|
||||
description: 接入端点标识(网关 SN,或独立设备自身标识)。排障时回答「这帧从哪个口进来的」。
|
||||
protocolVersion:
|
||||
type: string
|
||||
maxLength: 16
|
||||
|
||||
RawFrameBatchRequest:
|
||||
type: object
|
||||
required: [sourceChannel, frames]
|
||||
properties:
|
||||
sourceChannel:
|
||||
$ref: '#/components/schemas/SourceChannel'
|
||||
frames:
|
||||
type: array
|
||||
maxItems: 200
|
||||
items:
|
||||
$ref: '#/components/schemas/RawFrame'
|
||||
|
||||
DeliveryMode:
|
||||
type: string
|
||||
enum: [REALTIME, DEFERRED]
|
||||
description: |
|
||||
由业务层在创建命令时定,IoT 不判。
|
||||
`REALTIME` 实时命令(布撤防、重启):设备离线必须当场失败,不能「等上线再发」。
|
||||
`DEFERRED` 可延迟命令(配置下发):失败可重排。
|
||||
|
||||
CmdType:
|
||||
type: string
|
||||
description: 下行命令字 C1–C8。**IoT 只负责编码下发,不解释语义。**
|
||||
enum: [set_mode, set_ack_policy, time_sync, set_report_cfg, del_sensor, reboot, diag_dump, set_ota]
|
||||
|
||||
Command:
|
||||
type: object
|
||||
required: [commandId, gatewayId, targetId, cmdType, deliveryMode, leaseExpireAt, expireAt, retryCount]
|
||||
properties:
|
||||
commandId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
description: 命令唯一标识。**IoT 与网关都要按它去重**——租约回收可能导致同一条被下发两次。
|
||||
example: CMD-20261030-000123
|
||||
gatewayId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
description: |
|
||||
**「这条命令从哪个端点进去」,不是「网关 SN」。**
|
||||
连网关的哨兵填网关 SN;独立设备填设备自身标识;H 线填厂家云节点标识。
|
||||
IoT 拿它去自己的连接表里查,查不到报 `DEVICE_OFFLINE`。
|
||||
targetId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
description: 目标设备标识。路由在业务层写指令时就算完了,IoT 不需要知道它与 `gatewayId` 的关系。
|
||||
cmdType:
|
||||
$ref: '#/components/schemas/CmdType'
|
||||
deliveryMode:
|
||||
$ref: '#/components/schemas/DeliveryMode'
|
||||
params:
|
||||
type: object
|
||||
nullable: true
|
||||
additionalProperties: true
|
||||
leaseExpireAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 租约到期时刻(拉走 + 60s)。到期不回报会自动回「待下发」,`reclaimCount + 1`。
|
||||
expireAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: 命令最终有效期。过期即终态「已过期」。
|
||||
retryCount:
|
||||
type: integer
|
||||
description: 设备侧已失败次数。**与 `reclaimCount` 分开记**——混成一个就分不清是设备坏了还是中间件坏了。
|
||||
claimGeneration:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: |
|
||||
**领取代次(V1.7.2 新增):这条命令被领走第几次。** 每次 `PENDING → CLAIMED` 时 +1。
|
||||
|
||||
IoT 必须原样保存,并在 I-05 里回带。用途:一条命令租约到期被回收重派后,
|
||||
第一个节点的迟到回执会覆盖第二次的流转且不报错——回带代次后服务端能把它挡住。
|
||||
|
||||
不回带也不会被拒(兼容未升级的接入端),但等于没有代次校验。
|
||||
example: 1
|
||||
|
||||
PendingResponse:
|
||||
type: object
|
||||
required: [nodeId, commands, hasMore]
|
||||
properties:
|
||||
nodeId:
|
||||
type: string
|
||||
commands:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Command'
|
||||
hasMore:
|
||||
type: boolean
|
||||
description: 还有更多(本次达到 `limit` 上限)。IoT 可立即再拉一次。
|
||||
|
||||
CommandResult:
|
||||
type: object
|
||||
required: [commandId, result, claimGeneration]
|
||||
properties:
|
||||
commandId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
result:
|
||||
type: string
|
||||
enum: [ACKED, SENT, TIMEOUT, DEVICE_OFFLINE, FAILED]
|
||||
description: |
|
||||
- `ACKED` 设备有回应(看 `resultCode`)
|
||||
- `SENT` 已发出,本协议无回执通道
|
||||
- `TIMEOUT` 已发出,等不到回执
|
||||
- `DEVICE_OFFLINE` **没有发出去**
|
||||
- `FAILED` IoT 自身发送失败
|
||||
resultCode:
|
||||
type: string
|
||||
enum: ['0', '1', '2', '3', '4']
|
||||
nullable: true
|
||||
description: |
|
||||
**标准字典值,由 IoT 翻译后填,不是厂家原码。**
|
||||
`0` 成功/`1` 不支持/`2` 参数非法/`3` 执行失败/`4` 设备忙。
|
||||
厂家原始返回码请放 `rawResultCode`——直透到标准字段,业务层与前端就得按每个厂家
|
||||
各写一套判断,等于把适配器的活摊到全平台。
|
||||
|
||||
过渡期说明:服务端按字符串接收,**不因非标准值拒整批**(避免联调期因为一个码值
|
||||
整批 400),非标准值会记日志并在 `rawResultCode` 为空时兜底采用。
|
||||
rawResultCode:
|
||||
type: string
|
||||
maxLength: 32
|
||||
nullable: true
|
||||
description: |
|
||||
厂家原始返回码,**原样字符串,不解释**。
|
||||
V1.7 新增的**可选**字段:不填也能跑(`null` 时业务层只看 `resultCode`)。
|
||||
resultMsg:
|
||||
type: string
|
||||
maxLength: 256
|
||||
nullable: true
|
||||
ackAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: 收到回执的时刻。`SENT`/`DEVICE_OFFLINE`/`FAILED` 时为 `null`。
|
||||
rawAck:
|
||||
type: string
|
||||
maxLength: 512
|
||||
nullable: true
|
||||
description: 回执帧原样(hex)。排障用。
|
||||
claimGeneration:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: |
|
||||
领取时 I-18 给的 `claimGeneration`,**原样回带。V1.8 起 required,不可为空。**
|
||||
|
||||
服务端用它当 `WHERE claim_generation = ?` 的条件,挡住旧代次的迟到回执
|
||||
(更新 0 行、计 `duplicated`)。**缺失直接拒**,原因 `CLAIM_GENERATION_REQUIRED`——
|
||||
旧实现"缺了就拿库里当前值顶上",等于让代次校验自己失效,已取消。
|
||||
|
||||
与 `nodeId` 一起构成一次领取的唯一事实:
|
||||
`claim_generation` 回答"第几轮",`nodeId` 回答"这一轮是谁"。
|
||||
example: 1
|
||||
|
||||
ResultBatchRequest:
|
||||
type: object
|
||||
required: [nodeId, results]
|
||||
properties:
|
||||
nodeId:
|
||||
type: string
|
||||
maxLength: 64
|
||||
description: |
|
||||
**来源节点标识,必填。** 必须与 I-18 领取时落库的 `claimed_node_id` 一致,
|
||||
否则整条回执按 `NODE_MISMATCH` 拒收;I-18 时未落节点的历史行按 `NODE_UNKNOWN` 拒收。
|
||||
|
||||
**作用边界**:它不是身份认证——值由 IoT 自报,同样可以伪造;
|
||||
"调用方是谁"由内网来源白名单 + 签名保证。这里校验的是**领取事实的一致性**:
|
||||
这条结果是不是领走这一轮的那个节点回的。
|
||||
results:
|
||||
type: array
|
||||
maxItems: 50
|
||||
items:
|
||||
$ref: '#/components/schemas/CommandResult'
|
||||
|
||||
RejectReason:
|
||||
type: string
|
||||
enum: [RATE_LIMITED, SCHEMA_INVALID, UNKNOWN_CHANNEL, SENSOR_ID_INVALID,
|
||||
COMMAND_NOT_FOUND, CLAIM_GENERATION_REQUIRED, NODE_MISMATCH, NODE_UNKNOWN]
|
||||
description: |
|
||||
前四个用于 I-01/I-02/I-12(事件侧),后四个用于 I-05(命令回执侧):
|
||||
`COMMAND_NOT_FOUND` 命令号不存在;`CLAIM_GENERATION_REQUIRED` 缺 `claimGeneration`;
|
||||
`NODE_MISMATCH` 回执节点与领取节点不符;`NODE_UNKNOWN` 历史在途行没有领取节点可核。
|
||||
|
||||
**不要按原因做分支。** 收到 `rejectedDetail` 一律**不重试**,落死信 + 计数即可。
|
||||
唯一例外是 `NODE_UNKNOWN`:那条命令会在 60 秒租约到期后被回收重派,重领后节点列即有值。
|
||||
需要重试的只有整批失败(网络、5xx)。
|
||||
|
||||
RejectedItem:
|
||||
type: object
|
||||
required: [reason]
|
||||
properties:
|
||||
eventId:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 事件类批次用 `eventId` 定位。
|
||||
rawRef:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 原文类批次用 `rawRef` 定位。
|
||||
sensorId:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 状态类批次用 `sensorId` 定位。
|
||||
commandId:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 结果类批次用 `commandId` 定位。
|
||||
reason:
|
||||
$ref: '#/components/schemas/RejectReason'
|
||||
|
||||
IngestResponse:
|
||||
type: object
|
||||
required: [code, accepted, rejected, duplicated, unregistered]
|
||||
description: |
|
||||
**HTTP 200 只表示受理,不表示处理完。**
|
||||
`unregistered` 是**计数字段,不是错误**——业务层已写进 `unregistered_device`,
|
||||
接入侧不做任何动作:不重报、不换接口、不缓存。
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
description: 0 = 受理完成。非 0 见 `message`。
|
||||
example: 0
|
||||
accepted:
|
||||
type: integer
|
||||
description: 成功入库条数。
|
||||
example: 198
|
||||
rejected:
|
||||
type: integer
|
||||
description: 拒收条数。
|
||||
example: 2
|
||||
duplicated:
|
||||
type: integer
|
||||
description: 命中幂等跳过的条数(重推)。
|
||||
example: 3
|
||||
unregistered:
|
||||
type: integer
|
||||
description: 未建档设备条数。**不是错误。**
|
||||
example: 5
|
||||
rejectedDetail:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RejectedItem'
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
description: |
|
||||
错误码。**本文件暂不固定具体码值**——待与业务层错误码表统一后回填,
|
||||
当前按 HTTP 状态码区段理解:400xx 请求非法、401xx 鉴权失败、429xx 限流。
|
||||
message:
|
||||
type: string
|
||||
description: 人读的错误说明。**不要用它做分支判断**,用 `code`。
|
||||
Reference in new issue
Block a user