状态:设计待实施
适用对象:车牌识别相机、道闸控制器、边缘网关等支持局域网配置的设备
关联文档:doc/设备端对接文档.md、doc/设备接入层设计.md
当前 MQTT 接入方式要求边缘设备预先知道管理系统的 Broker 地址,再由设备主动建立 MQTT 连接。该前置配置对现场部署不友好,尤其是在管理系统 IP 变化、设备批量安装或设备恢复出厂设置时。
本方案增加一条只用于首次接入和重新配置的局域网通道,使管理系统可以发现待接入设备,并向设备下发 MQTT 连接参数。设备完成配置后,仍按既有 MQTT 协议工作;车辆识别、开关闸、状态上报和支付等正常业务流程不使用发现通道。
本期范围:
不在本期范围:
设备在不知道 Broker 地址时尚未连接 MQTT。此时管理系统无法通过 MQTT 获得设备 IP,也无法通过 MQTT 下发 Broker 配置。因此,系统主动获取 IP 并下发连接参数,必须建立在边缘端预置接入代理的前提上。
完整过程如下:
UDP 广播优于扫描整个 IP 网段:不需要假设网段范围,不会主动连接无关设备,响应速度快,也便于交换机和防火墙按固定端口放行。
UDP 发现失败时,系统不能把“没有发现设备”解释为“设备不存在”。管理员应切换到手工 IP 接入,通过 HTTPS 直接读取设备身份并完成同一套核验、绑定、配置和 MQTT 上线确认。
管理系统 边缘设备
| |
| UDP 广播 discovery.request.v1 |
|--------------------------------------------->|
| |
| UDP 单播 discovery.response.v1 |
|<---------------------------------------------|
| |
| 管理员确认设备、停车场、岗亭和通道 |
| |
| HTTPS POST /api/v1/provision |
|--------------------------------------------->|
| | 保存配置、重启 MQTT 客户端
| |
| MQTT CONNECT |
|<---------------------------------------------|
| |
| MQTT retained gate.state.v1 |
|<---------------------------------------------|
| |
| 接入状态更新为 已接入/在线 |
| 通道 | 用途 | 生命周期 | 可靠性要求 |
|---|---|---|---|
| UDP 31001 | 发现待接入设备 | 仅配置前或人工重新配置 | 可重试,不承载敏感配置 |
| HTTPS 8443 | 下发、查询和撤销设备配置 | 配置阶段 | 必须鉴权、签名、幂等 |
| MQTT 1883 或 TLS 8883 | 状态、识别事件、指令和回执 | 正常运营阶段 | QoS 1、LWT、retained state |
端口是建议默认值,最终允许设备固件和系统配置覆盖。管理系统侧端口必须在 Windows 防火墙和现场网络策略中放通。
设备接入状态与设备在线状态是两个不同维度,不能继续只使用现有设备表中的 online 或 offline。
| 接入状态 provision_status | 含义 | 允许操作 |
|---|---|---|
| unprovisioned | 已发现但未绑定业务通道 | 查看详情、发起配置 |
| provisioning | 管理系统正在下发配置 | 查询进度、取消或重试 |
| provisioned | 设备已确认保存配置,尚未收到 MQTT 在线状态 | 修改配置、等待上线 |
| online | MQTT gate.state.v1 已确认 | 正常通行、重新配置 |
| offline | 已接入设备当前无 MQTT 状态 | 查看原因、重新配置 |
| failed | 最近一次配置失败 | 查看错误、修正后重试 |
在线状态仍由 MQTT 维护。管理系统启动时会将启用 MQTT 设备暂置为 offline,收到 retained 的 gate.state.v1 后恢复 online。配置接口返回成功不等于设备已在线,必须收到 MQTT 状态消息才能认为接入完成。
建议在现有 uhf_reader 中补充以下字段,避免新建一张与设备主数据重复的表。
| 字段 | 类型 | 说明 |
|---|---|---|
| provision_status | varchar(20) | 接入状态,默认 unprovisioned |
| provision_method | varchar(20) | 接入方式,取 auto_udp 或 manual_ip |
| provision_error | varchar(500) | 最近一次失败原因 |
| provisioned_at | datetime | 设备确认保存配置的时间 |
| last_discovered_at | datetime | 最近一次发现响应时间 |
| discovered_ip | varchar(50) | 最近一次发现到的 IP,仅作辅助信息 |
| config_port | int | 设备 HTTPS 配置端口 |
| device_model | varchar(100) | 设备型号 |
| firmware_version | varchar(100) | 固件版本 |
| device_public_key | text | 设备身份公钥或证书指纹 |
现有 ip_address 保留为管理员确认后的设备管理地址。discovered_ip 是一次发现结果,未经管理员确认时不能覆盖已生效的管理地址。
建议增加 device_discovery 临时表保存发现结果,字段至少包括 request_id、device_id、source_ip、payload、verified、expired_at 和 created_at。发现记录默认 10 分钟过期,管理员确认绑定后再创建或更新 uhf_reader。
{
"schema": "discovery.request.v1",
"request_id": "7ba3a5ea-3ca4-45fe-b59d-1dc47b6abda5",
"issued_at": 1760000000,
"expires_at": 1760000030,
"system_id": "parking-system-001",
"nonce": "base64-random-32-bytes"
}
| 字段 | 必填 | 说明 |
|---|---|---|
| schema | 是 | 固定 discovery.request.v1 |
| request_id | 是 | UUID,用于关联响应 |
| issued_at / expires_at | 是 | Unix 秒,最长有效期 30 秒 |
| system_id | 是 | 管理系统安装实例标识 |
| nonce | 是 | 32 字节随机值,防止响应重放 |
{
"schema": "discovery.response.v1",
"request_id": "7ba3a5ea-3ca4-45fe-b59d-1dc47b6abda5",
"device_id": "SN-20260819-0001",
"device_code": "EDGE-A01",
"device_type": "lpr-gate",
"device_model": "LPR-GW-200",
"firmware_version": "2.1.0",
"ip": "192.168.10.26",
"config_url": "https://192.168.10.26:8443/api/v1/provision",
"public_key_fingerprint": "SHA256:...",
"signing_public_key": "base64-ed25519-public-key",
"nonce": "base64-random-32-bytes",
"signature": "base64-signature"
}
管理系统校验规则:
signing_public_key 必须是 Base64 编码的 Ed25519 公钥,其 SHA256 必须与 public_key_fingerprint 一致;签名载荷以换行符连接 schema、request_id、device_id、ip、config_url、nonce,顺序固定。首次信任仍须通过扫描设备标签二维码或输入一次性配对码完成。管理系统使用 HTTPS 调用设备的 POST /api/v1/provision。不能使用裸 TCP 或 UDP 承载配置命令,因为 Broker 地址、设备凭据和通道坐标均属于敏感配置。
首次部署可采用设备出厂自签名证书,但管理系统必须将响应中的公钥指纹与设备标签或二维码中的指纹比对。生产环境推荐每台设备预置证书并使用双向 TLS。
{
"schema": "provision.request.v1",
"request_id": "f0f2f011-cc14-4965-8a61-9a65b3ec2f0b",
"issued_at": 1760000000,
"expires_at": 1760000060,
"device_id": "SN-20260819-0001",
"device_code": "CAM-A01",
"mqtt": {
"host": "192.168.10.10",
"port": 8883,
"tls": true,
"client_id": "CAM-A01",
"username": "device/CAM-A01",
"password": "one-time-bootstrap-token",
"keep_alive_seconds": 30
},
"route": {
"parking_lot_id": 2,
"booth_id": 2,
"channel_id": 2,
"direction": "in"
},
"topic_prefix": "parking",
"config_version": 1,
"nonce": "base64-random-32-bytes",
"signature": "base64-signature"
}
字段及处理要求:
{
"schema": "provision.response.v1",
"request_id": "f0f2f011-cc14-4965-8a61-9a65b3ec2f0b",
"result": "accepted",
"message": "configuration saved; mqtt reconnecting",
"config_version": 1,
"applied_at": 1760000002,
"signature": "base64-signature"
}
accepted 只表示设备已保存并准备重连,不代表 MQTT 已在线。管理系统在 30 秒内等待目标设备的 retained gate.state.v1;收到后标记 online,超时则标记 failed 并记录诊断原因。
设备保存配置并连接 Broker 后,必须按现有设备端对接文档执行以下动作:
第 4 步不可省略。设备发生过异常断线时,旧的 retained LWT 可能在管理系统重启订阅后覆盖新的在线状态。
新功能按领域放入 internal/modules/device-provisioning,不直接塞入旧 internal/service/uhf。
internal/modules/device-provisioning/
├── api.go
├── service/
│ ├── discovery.go
│ ├── provisioning.go
│ ├── signature.go
│ └── service.go
├── repository/
│ └── repo.go
└── model/
├── request/
└── response/
| 组件 | 职责 |
|---|---|
| discovery.go | 枚举本机 IPv4 网卡,发送定向广播,校验响应,缓存发现结果 |
| provisioning.go | 校验管理员授权和业务绑定,调用设备 HTTPS 接口,等待 MQTT 确认 |
| signature.go | HMAC 或 Ed25519 签名、Nonce、过期时间和证书指纹验证 |
| repository | 保存临时发现记录、接入状态及审计信息 |
| 既有 MQTT 模块 | 继续处理正常业务消息与在线状态,不处理 UDP 和 HTTPS 配置细节 |
以下 API 必须只授予管理员角色:
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /device-provisioning/discover | 开始一次 3 秒局域网发现 |
| GET | /device-provisioning/discoveries | 查询未过期发现结果 |
| POST | /device-provisioning/:device_id/verify | 校验二维码、配对码或证书指纹 |
| POST | /device-provisioning/:device_id/provision | 绑定停车场、岗亭、通道并下发 MQTT 配置 |
| GET | /device-provisioning/:device_id/status | 查询配置、MQTT 确认和错误详情 |
| POST | /device-provisioning/:device_id/revoke | 让设备删除 MQTT 凭据并回到待配置状态 |
所有接口记录操作日志,至少包含操作人、目标硬件 ID、发现源 IP、配置前后路由、执行结果和失败原因。
设备管理页面增加自动发现设备入口,但不替换既有 TCP 或串口手工新增方式。
设备编码冲突、通道已经绑定独占设备、设备型号不支持所选功能时,必须在配置下发前阻断。
手工 IP 接入与 UDP 自动发现是两条并列的生产接入路径。它不绕过身份核验、管理员授权、业务绑定或 MQTT 上线确认;唯一差别是设备地址由管理员输入,系统不依赖 UDP 广播获得地址。
以下任一情况成立时,管理员可以选择“手工输入 IP”入口:
手工 IP 接入应在发现任务无结果、发现明确失败或管理员确认网络不支持广播时使用。系统应在接入记录中保存 provision_method=manual_ip,便于审计和后续运维统计。
GET /api/v1/identity(或设备端对接文档约定的身份查询接口)读取设备永久 ID、型号、固件版本、当前设备编码、证书公钥指纹和配对状态;读取失败不得创建或覆盖设备主数据。gate.state.v1。收到且设备永久 ID、业务坐标和配置版本一致后标记“已接入/在线”;超时显示“配置已保存但未上线”,保留诊断和重试入口。校验必须在前端和后端各执行一次,后端校验结果为准。输入框只接受字面 IP,不解析主机名或通过 DNS 重定向到其他地址。
| 输入 | 处理 |
|---|---|
127.0.0.1、任意 IPv4 127.0.0.0/8 |
拒绝,禁止连接管理系统本机回环地址 |
localhost(不区分大小写)及其他主机名 |
拒绝,手工入口只接受 IP |
0.0.0.0、IPv4 未指定地址 0.0.0.0/32 |
拒绝,不允许把监听地址当作设备地址 |
IPv4 定向广播地址(按管理系统网卡子网计算的主机位全 1)和 255.255.255.255 |
拒绝,禁止把广播地址当作单台设备 |
IPv4 组播 224.0.0.0/4、IPv6 组播 ff00::/8 |
拒绝,禁止通过组播地址连接配置接口 |
IPv6 回环 ::1、未指定地址 :: |
拒绝;若设备支持 IPv6,必须使用字面地址并按 HTTPS URL 规则加方括号 |
| 其他合法单播 IP | 允许进入 HTTPS 身份读取阶段,但仍须通过证书指纹、永久 ID 和配对码确认 |
| HTTPS 端口 | 仅允许 1-65535 的整数;连接必须使用 HTTPS,端口不可为空、不可为负数或超范围 |
检测定向广播地址时使用管理系统当前网卡和路由表计算,不把任意普通主机地址误判为广播。连接期间禁止跟随 HTTP 重定向到不同主机;最终连接地址必须仍是管理员输入的 IP。
手工流程沿用 provision_status,并增加 provision_method 和阶段错误码,避免把“身份未确认”和“MQTT 未上线”混为同一种失败:
| 状态/阶段 | 含义与处理 |
|---|---|
| manual_input | 已提交 IP 和端口,正在做本地及服务端格式校验 |
| manual_connecting | 正在建立 HTTPS 连接和读取设备身份;超时可重试,不改变既有设备配置 |
| identity_unverified | 已读取身份,等待证书指纹和配对码确认;此状态禁止下发配置 |
| manual_verified | 身份已确认,等待管理员完成停车场/岗亭/通道绑定和二次确认 |
| provisioning | 已通过 HTTPS 下发 MQTT 配置,等待设备回执 |
| provisioned | 设备已确认保存配置,尚未收到 gate.state.v1 |
| online | 收到并校验目标设备的 gate.state.v1 |
| failed | 任一阶段失败;保存阶段、错误码、原始 IP、端口和 request_id,修正后可重试 |
IP 不合法、端口不可用、HTTPS 证书不匹配、设备永久 ID 与既有记录不一致、配对码错误、设备拒绝配置、Broker 不可达或 30 秒未上线,都必须显示可操作的原因。重试只能从失败阶段重新开始;身份未确认或身份冲突时,必须重新执行身份确认,不能复用旧的信任结果。
自动发现降低部署成本,也扩大局域网攻击面。以下是上线前最低要求:
| 场景 | 系统处理 |
|---|---|
| 未发现设备 | 显示未发现,提示检查供电、同网段、防火墙和 UDP 端口 |
| 响应 IP 与 UDP 源 IP 不一致 | 丢弃响应并记录安全日志 |
| 设备证书指纹不匹配 | 禁止配置,要求管理员重新确认物理身份 |
| HTTPS 配置超时 | 标记 failed,不修改既有已生效设备路由 |
| 设备返回已保存但 30 秒未 MQTT 上线 | 标记 provisioned 或 failed,提示检查 Broker IP、防火墙和凭据 |
| MQTT 连上后再次离线 | 复用既有 LWT、重连和状态恢复机制,更新 offline |
| 管理系统重启 | MQTT 设备先保守 offline,收到 retained gate.state.v1 后恢复 online;发现任务不恢复 |
| 网络 IP 变化 | 重新发现后展示新的 discovered_ip,管理员确认后可以重新下发配置 |
| UDP 自动发现不可用 | 转入手工 IP 入口;不降低 HTTPS、证书指纹、永久 ID、配对码和管理员授权要求 |
| 手工输入 IP 不合法或为回环/广播/组播地址 | 本地和服务端均拒绝,不能发起网络连接,并提示修正地址 |
| 手工 IP 的 HTTPS 端口拒绝连接或超时 | 保持既有配置和绑定不变,状态为 failed,记录端口、错误码并提供重试 |
| 手工 IP 身份接口返回的永久 ID 已绑定其他设备 | 阻断流程并记录安全审计,不得覆盖原设备或按新设备创建 |
| 手工 IP 证书指纹或配对码不匹配 | 状态为 identity_unverified/failed,禁止下发 MQTT 配置,要求重新核对现场标签 |
验收:模拟边缘端能响应发现请求,管理系统能校验并保存发现记录。
验收:管理员可发现多个模拟设备;在跨 VLAN 或 UDP 被禁时,可通过手工 IP 完成身份核验和通道绑定;未核验设备不能下发配置。
验收:未配置设备可绑定到指定通道并上线;错误 Broker 地址能诊断为配置已保存但未上线。
验收:发现、身份确认、配置、MQTT 在线、车牌入场、开闸回执、设备离线和重连恢复全流程可重复通过。
| 分类 | 用例 | 预期结果 |
|---|---|---|
| 发现 | 同一网段 1 台设备 | 3 秒内出现 1 条待接入记录 |
| 发现 | 同一网段 20 台设备同时响应 | 按 device_id 去重展示,无阻塞 |
| 发现 | 伪造 request_id 或过期响应 | 被丢弃并记录安全日志 |
| 发现 | 响应载荷大于 4KB | 被丢弃,不影响服务 |
| 配置 | 正确签名和证书 | 设备保存配置并 MQTT 上线 |
| 配置 | 错误签名、过期时间或硬件 ID | 设备拒绝,系统显示失败原因 |
| 配置 | 相同 request_id 重试 | 设备幂等返回,不重复重启或创建凭据 |
| MQTT | Broker 地址为 127.0.0.1 | 下发前拒绝 |
| MQTT | Broker 防火墙未放行 | 配置保存后 30 秒未上线,给出诊断 |
| 恢复 | 管理系统重启 | retained state 使设备恢复在线 |
| 恢复 | 设备断电再上线 | LWT 离线,清除旧 LWT 并发布 state 后恢复在线 |
| 权限 | 操作员调用配置 API | 无权限,不能下发配置 |
| 手工接入 | 输入 127.0.0.1、localhost、0.0.0.0 |
前后端均拒绝,不发起 HTTPS 连接 |
| 手工接入 | 输入 IPv4 定向广播、255.255.255.255 或 IPv4/IPv6 组播地址 |
拒绝并提示地址不能指向广播或组播 |
| 手工接入 | 输入合法单播 IP 和有效 HTTPS 端口 | 成功读取身份,进入证书/配对码确认,不自动下发配置 |
| 手工接入 | 端口为空、非整数、0、负数或大于 65535 | 拒绝提交,不能建立连接 |
| 手工接入 | HTTPS 证书指纹与标签不一致 | 阻断配置并写入安全审计,设备保持原配置 |
| 手工接入 | 永久 ID 与已有设备记录不一致或已被绑定 | 阻断配置,不创建重复设备,不覆盖原绑定 |
| 手工接入 | 配对码错误、过期或重复使用 | 身份保持未确认,限制重试并记录审计 |
| 手工接入 | 正确身份确认后绑定停车场/岗亭/通道并下发 | 设备返回 accepted,等待 gate.state.v1 后才标记 online |
| 手工接入 | HTTPS 配置成功但 30 秒未收到 gate.state.v1 |
标记 provisioned 或 failed,展示 Broker、防火墙和凭据诊断,可重试 |
| 手工接入 | 非管理员访问手工接入、身份确认或配置 API | 返回无权限,不读取身份、不下发配置,并记录审计 |
| 手工接入 | 管理员完成一次成功接入 | 审计包含操作人、IP、端口、证书指纹、永久 ID、绑定和结果,不记录令牌明文 |
实施前必须确认以下事项:
在上述前提未确认前,不应将主动获取 IP、下发 Broker 地址的逻辑直接写入既有 MQTT 开关闸代码。该能力属于设备首次接入和配置管理,不属于正常通行控制。