feat(大屏): 设备绑定流 — 永久设备ID+待绑定页(连接码)+MQTT绑定确认

- config.getDeviceId:首启生成并持久化永久设备ID,作为 MQTT client_id(稳定可控)。
- tenant.js 改设备绑定:registerDevice/requestBindCode/applyBound(存 token+tenant)/isDeviceBound。
- mqtt.js:client_id=device_id;订阅 bind/<dev>(未绑定确认)+command/tick(租户)。
- main.jsx:未绑定→BindScreen(注册+取8位码+监听绑定频道, 绑定后 reload);已绑定→App。
- BindScreen:展示 8 位连接码+设备ID+等待园区端绑定。yarn build 通过。
This commit is contained in:
Pine
2026-08-24 20:14:15 +08:00
parent 225c22ad62
commit 4257db2e26
5 changed files with 101 additions and 36 deletions
+6 -5
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import mqtt from 'mqtt';
import { getMqttUrl, getMqttCredentials, MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK } from '../config';
import { getMqttUrl, getMqttCredentials, getDeviceId, MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK, MQTT_TOPIC_BIND } from '../config';
import { getTenantId } from './tenant';
/* =========================================================
@@ -32,8 +32,8 @@ function makeClientId() {
export function connectMqtt() {
if (client) return;
try {
// 独立客户端 ID:避免多屏/多实例/多标签页同 ID 被 broker 互相踢下线
clientId = makeClientId();
// 永久设备 ID 作为 MQTT 客户端名(稳定、可被园区端精准控制)
clientId = getDeviceId();
// 连接时惰性读取 MQTT 地址/账号:打包部署时 main.jsx 引导已从后端 /api/config 覆盖
const { username, password } = getMqttCredentials();
client = mqtt.connect(getMqttUrl(), {
@@ -47,10 +47,11 @@ export function connectMqtt() {
client.on('connect', () => {
status = 'connected';
notifyStatus();
// 多租户:只监听本园区频道 opc/display/command/<tenant_id>/<clientId>(+ 本园区数据 tick)。
// 不再订阅全局命令频道 → 后端按租户+客户端路由时,他屏/他园区的指令绝收不到。
// 多租户:只监听本园区频道 command/<tenant_id>/<clientId>(+ tick/<tenant_id>);
// 未绑定时仍订阅 绑定频道 bind/<clientId>(设备绑定确认),并接收全局 tick 回退。
const tid = getTenantId();
client.subscribe([
`${MQTT_TOPIC_BIND}/${clientId}`, // 绑定确认(未绑定/重绑)
tid ? `${MQTT_TOPIC_TICK}/${tid}` : MQTT_TOPIC_TICK,
tid ? `${MQTT_TOPIC_COMMAND}/${tid}/${clientId}` : `${MQTT_TOPIC_COMMAND}/${clientId}`,
], { qos: 1 });