@microi.net/cli 4.9.6 → 4.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +6 -5
- package/package.json +1 -1
- package/scripts/mcp-server.js +83 -83
- package/scripts/microi-cli.js +55 -85
- package/scripts/microi-codex-broker.js +418 -0
- package/scripts/microi-codex-router.js +129 -65
- package/scripts/microi-skills.meta.json +310 -151
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +3566 -0
- package/skills/ai-platform-governance/SKILL.md +21 -166
- package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -0
- package/skills/microi-client-frontend/SKILL.md +17 -434
- package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +144 -0
- package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +178 -0
- package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +144 -0
- package/skills/microi-db-schema/SKILL.md +3 -3
- package/skills/microi-db-schema/references/schema-overview.md +1 -1
- package/skills/microi-db-schema/references/schema.md +1 -1
- package/skills/microi-db-schema/references/table-catalog.md +1 -1
- package/skills/microi-form-engine/SKILL.md +1 -1
- package/skills/microi-form-layout/SKILL.md +19 -225
- package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -0
- package/skills/microi-frontend-sdk/SKILL.md +17 -151
- package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +171 -0
- package/skills/microi-mobile-app-quality/SKILL.md +22 -288
- package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +209 -0
- package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +117 -0
- package/skills/microi-system-delivery/SKILL.md +16 -380
- package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +186 -0
- package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +210 -0
- package/skills/microi-ui/SKILL.md +19 -169
- package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -0
- package/skills/microi-uniapp-frontend/SKILL.md +26 -335
- package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +225 -0
- package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +154 -0
- package/skills/page-engine/SKILL.md +23 -271
- package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +234 -0
- package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +60 -0
- package/skills/playwright-e2e/SKILL.md +24 -590
- package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +173 -0
- package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +183 -0
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -0
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +69 -0
- package/skills/scripts/optimize-progressive-disclosure.mjs +204 -0
- package/skills/scripts/refresh-progressive-disclosure.mjs +64 -0
- package/skills/scripts/validate-progressive-disclosure.mjs +52 -0
- package/skills/ui-design/SKILL.md +26 -1461
- package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -0
- package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +155 -0
- package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -0
- package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -0
- package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -0
- package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -0
- package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -0
- package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -0
- package/skills/v8-crud-api/SKILL.md +20 -245
- package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +226 -0
- package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -0
- package/skills/v8-export-import/SKILL.md +15 -425
- package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +211 -0
- package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -0
- package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +42 -0
- package/skills/v8-file-upload/SKILL.md +16 -354
- package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +227 -0
- package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +149 -0
- package/skills/v8-frontend-events/SKILL.md +19 -205
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -0
- package/skills/v8-http-integration/SKILL.md +14 -236
- package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -0
- package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +44 -0
- package/skills/v8-menu-buttons/SKILL.md +15 -511
- package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +221 -0
- package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +224 -0
- package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +104 -0
- package/skills/v8-mq-mqtt/SKILL.md +11 -175
- package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -0
- package/skills/v8-security/SKILL.md +16 -329
- package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +199 -0
- package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +158 -0
- package/skills/v8-table-event/SKILL.md +16 -236
- package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +216 -0
- package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +46 -0
- package/skills/v8-workflow/SKILL.md +19 -160
- package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -0
- package/skills/workspace-conventions/SKILL.md +29 -361
- package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +208 -0
- package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +196 -0
- package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +27 -0
|
@@ -9,6 +9,8 @@ description: Microi V8 消息队列与 MQTT 生产指南。用于 V8.MQ.SendMsg
|
|
|
9
9
|
|
|
10
10
|
你正在开发 Microi 吾码平台的 V8 引擎代码,需要使用 RabbitMQ 消息队列或 MQTT 物联网协议。
|
|
11
11
|
|
|
12
|
+
<!-- microi-progressive:begin -->
|
|
13
|
+
<!-- microi-progressive:chunk id=v8-mq-mqtt-000 sha256=a883bb502b306ada654b8f45bd6f955c84049f3a032568e5ef8e25e24aa1c2c6 -->
|
|
12
14
|
## V8.MQ — RabbitMQ 消息队列
|
|
13
15
|
|
|
14
16
|
### 生产消息(后端)
|
|
@@ -129,181 +131,8 @@ try {
|
|
|
129
131
|
|
|
130
132
|
---
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
先区分两个边界:MQTT 是设备实时双向通道,RabbitMQ 是服务端异步队列;
|
|
135
|
-
RS-485、ZigBee、BLE、Modbus 等现场协议需先由网关转换为 MQTT。涉及 MQTT
|
|
136
|
-
配置、安全、设备级路由、生产部署或故障排查时,必须继续读取
|
|
137
|
-
[MQTT 生产参考](references/mqtt-production.md),不要只凭下面的快速示例上线。
|
|
138
|
-
|
|
139
|
-
### 快速实施顺序
|
|
140
|
-
|
|
141
|
-
1. 先确定拓扑:单节点/独立 MQTT 节点可用内嵌 Broker;多 API 节点不要把
|
|
142
|
-
各节点的会话、订阅和 Retained Message 误认为一个集群。
|
|
143
|
-
2. 在 SaaS 引擎为主租户启用监听,并为每个接入租户配置独立完整的
|
|
144
|
-
`MqttAccount`、`MqttPwd` 与 `MqttApiEngine`。
|
|
145
|
-
3. 让设备携带 MQTT v5 `OsClient`,或使用 `<OsClient>:<账号>` 用户名、
|
|
146
|
-
`<OsClient>:<设备Id>` ClientId;多个来源同时存在时必须指向同一租户。
|
|
147
|
-
4. 在接口引擎按 `V8.EventName` 路由,并只读取 Broker 已校验的 `V8.MQTT`;
|
|
148
|
-
不要从 Payload 重新信任租户、Topic 或设备身份。
|
|
149
|
-
5. 用真实客户端验证 TCP/TLS、错误凭据、跨租户 Topic、QoS、Retain、快速重连、
|
|
150
|
-
V8 拒绝、重复消息和节点重启,不能用静态检查代替 Broker/硬件验收。
|
|
151
|
-
|
|
152
|
-
主租户运行时读取 `MqttPort`(默认 `1883`)以及可选 TLS 配置;子租户自己的
|
|
153
|
-
端口不会再启动一套 Broker。`MqttWsPort` 是保留元数据,当前内嵌 Broker 没有
|
|
154
|
-
启用 WebSocket 监听。
|
|
155
|
-
|
|
156
|
-
### MQTT 事件类型
|
|
157
|
-
|
|
158
|
-
MQTT 通过一个接口引擎处理所有事件,通过 `V8.EventName` 判断当前事件类型:
|
|
159
|
-
|
|
160
|
-
| V8.EventName | 说明 |
|
|
161
|
-
|---|---|
|
|
162
|
-
| `StartServer` | MQTT 服务启动 |
|
|
163
|
-
| `Connected` | 客户端连接 |
|
|
164
|
-
| `Disconnected` | 客户端断开连接 |
|
|
165
|
-
| `Subscribing` | Topic 通过 Broker ACL 后发生订阅;用于观察与审计,不承担拒绝语义 |
|
|
166
|
-
| `MessageReceived` | 收到客户端消息 |
|
|
167
|
-
| `MessageChanged` | Retained Message 发生变化 |
|
|
168
|
-
| `StopServer` | MQTT 服务停止 |
|
|
169
|
-
|
|
170
|
-
### V8.MQTT 上下文
|
|
171
|
-
|
|
172
|
-
| 属性 | 说明 |
|
|
173
|
-
|---|---|
|
|
174
|
-
| `V8.MQTT.ClientId` | 客户端 Id |
|
|
175
|
-
| `V8.MQTT.OsClient` | Broker 已校验的租户标识 |
|
|
176
|
-
| `V8.MQTT.Topic` | 规范化后的完整 Topic |
|
|
177
|
-
| `V8.MQTT.Payload` | JSON 自动解析后的对象,或解析失败时的字符串 |
|
|
178
|
-
| `V8.MQTT.PayloadRaw` | 原始 UTF-8 Payload 文本 |
|
|
179
|
-
| `V8.MQTT.UserName` | 连接事件中的客户端用户名 |
|
|
180
|
-
| `V8.MQTT.Qos` | QoS:`0`、`1` 或 `2` |
|
|
181
|
-
| `V8.MQTT.Retain` | 是否为 Retained Message |
|
|
182
|
-
| `V8.MQTT.UserProperties` | MQTT v5 User Properties;没有时为空 |
|
|
183
|
-
|
|
184
|
-
子租户必须 `MqttEnable=1` 并配置独立 `MqttAccount/MqttPwd`。子租户不能通过 `MqttAllowAnonymous=1` 或 `MqttTopicIsolation=0` 关闭边界;缺少完整凭据时拒绝连接。Topic 统一为 `tenant/{lowerOsClient}/{businessTopic}`,服务端 publish、subscribe、retained、ResponseTopic 都会校验并拒绝其它租户、`$SYS` 系统 Topic 和 `$share` 共享订阅绕过。
|
|
185
|
-
|
|
186
|
-
### 完整示例
|
|
187
|
-
|
|
188
|
-
```javascript
|
|
189
|
-
var eventName = V8.EventName;
|
|
190
|
-
|
|
191
|
-
if (eventName === 'StartServer') {
|
|
192
|
-
console.log('MQTT 服务已启动');
|
|
193
|
-
|
|
194
|
-
} else if (eventName === 'Connected') {
|
|
195
|
-
console.log('设备已连接: ' + V8.MQTT.ClientId);
|
|
196
|
-
// 记录设备在线状态
|
|
197
|
-
V8.FormEngine.UptFormDataByWhere('Device', {
|
|
198
|
-
_Where: [['DeviceCode', '=', V8.MQTT.ClientId]],
|
|
199
|
-
OnlineStatus: 1,
|
|
200
|
-
LastOnlineTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
} else if (eventName === 'Disconnected') {
|
|
204
|
-
console.log('设备已断开: ' + V8.MQTT.ClientId);
|
|
205
|
-
V8.FormEngine.UptFormDataByWhere('Device', {
|
|
206
|
-
_Where: [['DeviceCode', '=', V8.MQTT.ClientId]],
|
|
207
|
-
OnlineStatus: 0,
|
|
208
|
-
LastOfflineTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
} else if (eventName === 'Subscribing') {
|
|
212
|
-
console.log('设备订阅: ' + V8.MQTT.ClientId + ' -> ' + V8.MQTT.Topic);
|
|
213
|
-
|
|
214
|
-
} else if (eventName === 'MessageReceived') {
|
|
215
|
-
// 处理设备上报的数据
|
|
216
|
-
var clientId = V8.MQTT.ClientId;
|
|
217
|
-
var topic = V8.MQTT.Topic;
|
|
218
|
-
var payload = V8.MQTT.Payload;
|
|
219
|
-
|
|
220
|
-
if (typeof payload === 'string') {
|
|
221
|
-
try {
|
|
222
|
-
payload = JSON.parse(payload);
|
|
223
|
-
} catch (ex) {
|
|
224
|
-
return { Code: 0, Msg: 'Payload 必须是合法 JSON。' };
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (!payload || !payload.eventId) {
|
|
228
|
-
return { Code: 0, Msg: '缺少稳定的 eventId。' };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
console.log('收到消息: ' + clientId + ' - ' + topic);
|
|
232
|
-
|
|
233
|
-
// 存储到 MongoDB(适合海量数据)
|
|
234
|
-
V8.MongoDb.AddFormData({
|
|
235
|
-
DbName: 'iot_data',
|
|
236
|
-
TableName: 'device_msg_' + DateNow('yyyy_MM'),
|
|
237
|
-
_FormData: {
|
|
238
|
-
DeviceId: clientId,
|
|
239
|
-
EventId: payload.eventId,
|
|
240
|
-
Topic: topic,
|
|
241
|
-
Payload: payload,
|
|
242
|
-
PayloadRaw: V8.MQTT.PayloadRaw,
|
|
243
|
-
Qos: V8.MQTT.Qos,
|
|
244
|
-
Retain: V8.MQTT.Retain,
|
|
245
|
-
CreateTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// 解析特定主题的数据
|
|
250
|
-
var temperatureTopic = 'tenant/' + V8.OsClient.toLowerCase() + '/sensor/temperature';
|
|
251
|
-
if (topic === temperatureTopic) {
|
|
252
|
-
var temp = Number(payload.temperature);
|
|
253
|
-
if (isNaN(temp)) return { Code: 0, Msg: 'temperature 必须是数字。' };
|
|
254
|
-
if (temp > 80) {
|
|
255
|
-
// 温度报警
|
|
256
|
-
V8.ApiEngine.Run('send-alarm', {
|
|
257
|
-
eventId: payload.eventId,
|
|
258
|
-
deviceId: clientId,
|
|
259
|
-
type: 'temperature',
|
|
260
|
-
value: temp
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return { Code: 1 };
|
|
266
|
-
|
|
267
|
-
} else if (eventName === 'MessageChanged') {
|
|
268
|
-
console.log('Retained Message 已变化: ' + V8.MQTT.Topic);
|
|
269
|
-
|
|
270
|
-
} else if (eventName === 'StopServer') {
|
|
271
|
-
console.log('MQTT 服务已停止');
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
`MessageReceived` 中只有显式 `Code != 1` 会阻止向订阅者广播;无返回值、普通
|
|
276
|
-
字符串/数字、没有 `Code` 的对象和 `Code: 1` 保持兼容放行。已配置事件引擎但
|
|
277
|
-
执行异常时失败关闭。其它事件的返回值不改变连接、订阅或生命周期结果。
|
|
278
|
-
|
|
279
|
-
平台在 V8 前写入接收日志,因此被规则拒绝的消息仍可审计。业务副作用仍必须以
|
|
280
|
-
设备提供的稳定 `EventId` 配合唯一约束、inbox/outbox 或条件更新实现幂等;
|
|
281
|
-
MQTT QoS、Retain 和连接锁都不等于业务“恰好一次”。
|
|
282
|
-
|
|
283
|
-
### 设备、下行与部署边界
|
|
284
|
-
|
|
285
|
-
- 平台自动维护 `mci_mqtt_client` 与 `mci_mqtt_log`;前者支持设备级
|
|
286
|
-
`ApiEngineId` 覆盖租户 `MqttApiEngine`,修改后让设备重新连接刷新当前节点缓存。
|
|
287
|
-
- 可信 C# 后端只使用 `IMicroiMQTT.PublishAsync(osClient, ...)` 下行;缺少租户
|
|
288
|
-
上下文的旧重载会拒绝。`V8.MQTT` 当前是事件上下文,不是通用 V8 发布函数。
|
|
289
|
-
- 同一 ClientId 快速重连时,旧会话的延迟断开会记录为 `StaleDisconnectIgnored`,
|
|
290
|
-
不会把已接管的新会话误标为离线。
|
|
291
|
-
- `ConnectedClients` / `GetConnectedClients(osClient)` 和管理状态接口只表示当前
|
|
292
|
-
MQTT 节点快照,不能作为集群全局在线事实。
|
|
293
|
-
- 内嵌 Broker 的会话、订阅、Retained Message 不跨 API 节点共享。多节点生产
|
|
294
|
-
使用独立 MQTT 节点,或外部集群 Broker + 租户感知适配器,并保持业务幂等。
|
|
295
|
-
|
|
296
|
-
### 变更后的覆盖检查
|
|
297
|
-
|
|
298
|
-
修改 MQTT 运行时、官网文档或本 Skill 后运行:
|
|
299
|
-
|
|
300
|
-
```powershell
|
|
301
|
-
node microi.skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
该检查只证明源码中的事件/上下文字段和关键安全能力已进入文档与 Skill;它不能
|
|
305
|
-
证明 Broker、网络、证书、外部集群、真实设备或吞吐已经通过验收。
|
|
306
|
-
|
|
134
|
+
<!-- /microi-progressive:chunk -->
|
|
135
|
+
<!-- microi-progressive:chunk id=v8-mq-mqtt-001 sha256=1f51deb2673c77cdd3a096ca658dfe0f025531760f1aee04987f3bb2723131cf -->
|
|
307
136
|
## 注意事项
|
|
308
137
|
|
|
309
138
|
- MQ 消费者接口引擎通过 `V8.Param.Message` 获取消息,包含 `EventId`、兼容 `Id`、`OsClient`、`Message`、`CurrentUserId`
|
|
@@ -315,3 +144,10 @@ node microi.skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs
|
|
|
315
144
|
- `ConnectedClients` 只代表当前 MQTT 节点的诊断快照,不是集群全局在线事实
|
|
316
145
|
- 内嵌 MQTT Broker 不具备跨节点共享会话/订阅/retained 的集群一致性;多 API 节点生产部署应使用支持集群的外部 Broker,或把内嵌 Broker 固定到独立节点并由负载入口路由,不能让每个 API 节点各自充当一套独立 Broker
|
|
317
146
|
- MQTT 生产配置、安全语义、事件字段可用性和上线清单以 [MQTT 生产参考](references/mqtt-production.md) 为准
|
|
147
|
+
<!-- /microi-progressive:chunk -->
|
|
148
|
+
## 详细参考路由(渐进披露)
|
|
149
|
+
|
|
150
|
+
仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。
|
|
151
|
+
|
|
152
|
+
- [references/progressive-01-v8-mqtt-iot-物联网.md](references/progressive-01-v8-mqtt-iot-物联网.md):V8.MQTT — IoT 物联网
|
|
153
|
+
<!-- microi-progressive:end -->
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# v8-mq-mqtt 详细参考 1
|
|
2
|
+
|
|
3
|
+
> 按需读取;本文件由 SKILL.md 的原章节无损拆分。
|
|
4
|
+
|
|
5
|
+
<!-- microi-progressive:chunk id=v8-mq-mqtt-002 sha256=1fb410755c51211b4b978d64c9c88fc5649489f4bced8870ebbf255a31b75177 -->
|
|
6
|
+
## V8.MQTT — IoT 物联网
|
|
7
|
+
|
|
8
|
+
先区分两个边界:MQTT 是设备实时双向通道,RabbitMQ 是服务端异步队列;
|
|
9
|
+
RS-485、ZigBee、BLE、Modbus 等现场协议需先由网关转换为 MQTT。涉及 MQTT
|
|
10
|
+
配置、安全、设备级路由、生产部署或故障排查时,必须继续读取
|
|
11
|
+
[MQTT 生产参考](references/mqtt-production.md),不要只凭下面的快速示例上线。
|
|
12
|
+
|
|
13
|
+
### 快速实施顺序
|
|
14
|
+
|
|
15
|
+
1. 先确定拓扑:单节点/独立 MQTT 节点可用内嵌 Broker;多 API 节点不要把
|
|
16
|
+
各节点的会话、订阅和 Retained Message 误认为一个集群。
|
|
17
|
+
2. 在 SaaS 引擎为主租户启用监听,并为每个接入租户配置独立完整的
|
|
18
|
+
`MqttAccount`、`MqttPwd` 与 `MqttApiEngine`。
|
|
19
|
+
3. 让设备携带 MQTT v5 `OsClient`,或使用 `<OsClient>:<账号>` 用户名、
|
|
20
|
+
`<OsClient>:<设备Id>` ClientId;多个来源同时存在时必须指向同一租户。
|
|
21
|
+
4. 在接口引擎按 `V8.EventName` 路由,并只读取 Broker 已校验的 `V8.MQTT`;
|
|
22
|
+
不要从 Payload 重新信任租户、Topic 或设备身份。
|
|
23
|
+
5. 用真实客户端验证 TCP/TLS、错误凭据、跨租户 Topic、QoS、Retain、快速重连、
|
|
24
|
+
V8 拒绝、重复消息和节点重启,不能用静态检查代替 Broker/硬件验收。
|
|
25
|
+
|
|
26
|
+
主租户运行时读取 `MqttPort`(默认 `1883`)以及可选 TLS 配置;子租户自己的
|
|
27
|
+
端口不会再启动一套 Broker。`MqttWsPort` 是保留元数据,当前内嵌 Broker 没有
|
|
28
|
+
启用 WebSocket 监听。
|
|
29
|
+
|
|
30
|
+
### MQTT 事件类型
|
|
31
|
+
|
|
32
|
+
MQTT 通过一个接口引擎处理所有事件,通过 `V8.EventName` 判断当前事件类型:
|
|
33
|
+
|
|
34
|
+
| V8.EventName | 说明 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `StartServer` | MQTT 服务启动 |
|
|
37
|
+
| `Connected` | 客户端连接 |
|
|
38
|
+
| `Disconnected` | 客户端断开连接 |
|
|
39
|
+
| `Subscribing` | Topic 通过 Broker ACL 后发生订阅;用于观察与审计,不承担拒绝语义 |
|
|
40
|
+
| `MessageReceived` | 收到客户端消息 |
|
|
41
|
+
| `MessageChanged` | Retained Message 发生变化 |
|
|
42
|
+
| `StopServer` | MQTT 服务停止 |
|
|
43
|
+
|
|
44
|
+
### V8.MQTT 上下文
|
|
45
|
+
|
|
46
|
+
| 属性 | 说明 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `V8.MQTT.ClientId` | 客户端 Id |
|
|
49
|
+
| `V8.MQTT.OsClient` | Broker 已校验的租户标识 |
|
|
50
|
+
| `V8.MQTT.Topic` | 规范化后的完整 Topic |
|
|
51
|
+
| `V8.MQTT.Payload` | JSON 自动解析后的对象,或解析失败时的字符串 |
|
|
52
|
+
| `V8.MQTT.PayloadRaw` | 原始 UTF-8 Payload 文本 |
|
|
53
|
+
| `V8.MQTT.UserName` | 连接事件中的客户端用户名 |
|
|
54
|
+
| `V8.MQTT.Qos` | QoS:`0`、`1` 或 `2` |
|
|
55
|
+
| `V8.MQTT.Retain` | 是否为 Retained Message |
|
|
56
|
+
| `V8.MQTT.UserProperties` | MQTT v5 User Properties;没有时为空 |
|
|
57
|
+
|
|
58
|
+
子租户必须 `MqttEnable=1` 并配置独立 `MqttAccount/MqttPwd`。子租户不能通过 `MqttAllowAnonymous=1` 或 `MqttTopicIsolation=0` 关闭边界;缺少完整凭据时拒绝连接。Topic 统一为 `tenant/{lowerOsClient}/{businessTopic}`,服务端 publish、subscribe、retained、ResponseTopic 都会校验并拒绝其它租户、`$SYS` 系统 Topic 和 `$share` 共享订阅绕过。
|
|
59
|
+
|
|
60
|
+
### 完整示例
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
var eventName = V8.EventName;
|
|
64
|
+
|
|
65
|
+
if (eventName === 'StartServer') {
|
|
66
|
+
console.log('MQTT 服务已启动');
|
|
67
|
+
|
|
68
|
+
} else if (eventName === 'Connected') {
|
|
69
|
+
console.log('设备已连接: ' + V8.MQTT.ClientId);
|
|
70
|
+
// 记录设备在线状态
|
|
71
|
+
V8.FormEngine.UptFormDataByWhere('Device', {
|
|
72
|
+
_Where: [['DeviceCode', '=', V8.MQTT.ClientId]],
|
|
73
|
+
OnlineStatus: 1,
|
|
74
|
+
LastOnlineTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
} else if (eventName === 'Disconnected') {
|
|
78
|
+
console.log('设备已断开: ' + V8.MQTT.ClientId);
|
|
79
|
+
V8.FormEngine.UptFormDataByWhere('Device', {
|
|
80
|
+
_Where: [['DeviceCode', '=', V8.MQTT.ClientId]],
|
|
81
|
+
OnlineStatus: 0,
|
|
82
|
+
LastOfflineTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
} else if (eventName === 'Subscribing') {
|
|
86
|
+
console.log('设备订阅: ' + V8.MQTT.ClientId + ' -> ' + V8.MQTT.Topic);
|
|
87
|
+
|
|
88
|
+
} else if (eventName === 'MessageReceived') {
|
|
89
|
+
// 处理设备上报的数据
|
|
90
|
+
var clientId = V8.MQTT.ClientId;
|
|
91
|
+
var topic = V8.MQTT.Topic;
|
|
92
|
+
var payload = V8.MQTT.Payload;
|
|
93
|
+
|
|
94
|
+
if (typeof payload === 'string') {
|
|
95
|
+
try {
|
|
96
|
+
payload = JSON.parse(payload);
|
|
97
|
+
} catch (ex) {
|
|
98
|
+
return { Code: 0, Msg: 'Payload 必须是合法 JSON。' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!payload || !payload.eventId) {
|
|
102
|
+
return { Code: 0, Msg: '缺少稳定的 eventId。' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('收到消息: ' + clientId + ' - ' + topic);
|
|
106
|
+
|
|
107
|
+
// 存储到 MongoDB(适合海量数据)
|
|
108
|
+
V8.MongoDb.AddFormData({
|
|
109
|
+
DbName: 'iot_data',
|
|
110
|
+
TableName: 'device_msg_' + DateNow('yyyy_MM'),
|
|
111
|
+
_FormData: {
|
|
112
|
+
DeviceId: clientId,
|
|
113
|
+
EventId: payload.eventId,
|
|
114
|
+
Topic: topic,
|
|
115
|
+
Payload: payload,
|
|
116
|
+
PayloadRaw: V8.MQTT.PayloadRaw,
|
|
117
|
+
Qos: V8.MQTT.Qos,
|
|
118
|
+
Retain: V8.MQTT.Retain,
|
|
119
|
+
CreateTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 解析特定主题的数据
|
|
124
|
+
var temperatureTopic = 'tenant/' + V8.OsClient.toLowerCase() + '/sensor/temperature';
|
|
125
|
+
if (topic === temperatureTopic) {
|
|
126
|
+
var temp = Number(payload.temperature);
|
|
127
|
+
if (isNaN(temp)) return { Code: 0, Msg: 'temperature 必须是数字。' };
|
|
128
|
+
if (temp > 80) {
|
|
129
|
+
// 温度报警
|
|
130
|
+
V8.ApiEngine.Run('send-alarm', {
|
|
131
|
+
eventId: payload.eventId,
|
|
132
|
+
deviceId: clientId,
|
|
133
|
+
type: 'temperature',
|
|
134
|
+
value: temp
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { Code: 1 };
|
|
140
|
+
|
|
141
|
+
} else if (eventName === 'MessageChanged') {
|
|
142
|
+
console.log('Retained Message 已变化: ' + V8.MQTT.Topic);
|
|
143
|
+
|
|
144
|
+
} else if (eventName === 'StopServer') {
|
|
145
|
+
console.log('MQTT 服务已停止');
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`MessageReceived` 中只有显式 `Code != 1` 会阻止向订阅者广播;无返回值、普通
|
|
150
|
+
字符串/数字、没有 `Code` 的对象和 `Code: 1` 保持兼容放行。已配置事件引擎但
|
|
151
|
+
执行异常时失败关闭。其它事件的返回值不改变连接、订阅或生命周期结果。
|
|
152
|
+
|
|
153
|
+
平台在 V8 前写入接收日志,因此被规则拒绝的消息仍可审计。业务副作用仍必须以
|
|
154
|
+
设备提供的稳定 `EventId` 配合唯一约束、inbox/outbox 或条件更新实现幂等;
|
|
155
|
+
MQTT QoS、Retain 和连接锁都不等于业务“恰好一次”。
|
|
156
|
+
|
|
157
|
+
### 设备、下行与部署边界
|
|
158
|
+
|
|
159
|
+
- 平台自动维护 `mci_mqtt_client` 与 `mci_mqtt_log`;前者支持设备级
|
|
160
|
+
`ApiEngineId` 覆盖租户 `MqttApiEngine`,修改后让设备重新连接刷新当前节点缓存。
|
|
161
|
+
- 可信 C# 后端只使用 `IMicroiMQTT.PublishAsync(osClient, ...)` 下行;缺少租户
|
|
162
|
+
上下文的旧重载会拒绝。`V8.MQTT` 当前是事件上下文,不是通用 V8 发布函数。
|
|
163
|
+
- 同一 ClientId 快速重连时,旧会话的延迟断开会记录为 `StaleDisconnectIgnored`,
|
|
164
|
+
不会把已接管的新会话误标为离线。
|
|
165
|
+
- `ConnectedClients` / `GetConnectedClients(osClient)` 和管理状态接口只表示当前
|
|
166
|
+
MQTT 节点快照,不能作为集群全局在线事实。
|
|
167
|
+
- 内嵌 Broker 的会话、订阅、Retained Message 不跨 API 节点共享。多节点生产
|
|
168
|
+
使用独立 MQTT 节点,或外部集群 Broker + 租户感知适配器,并保持业务幂等。
|
|
169
|
+
|
|
170
|
+
### 变更后的覆盖检查
|
|
171
|
+
|
|
172
|
+
修改 MQTT 运行时、官网文档或本 Skill 后运行:
|
|
173
|
+
|
|
174
|
+
```powershell
|
|
175
|
+
node microi.skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
该检查只证明源码中的事件/上下文字段和关键安全能力已进入文档与 Skill;它不能
|
|
179
|
+
证明 Broker、网络、证书、外部集群、真实设备或吞吐已经通过验收。
|
|
180
|
+
|
|
181
|
+
<!-- /microi-progressive:chunk -->
|