All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m12s
1. 新增 MQTT 即時通訊與 Topic 規範文件 (.agents/skills/mqtt-communication-specs/SKILL.md)。 2. 建立 MQTT 基礎架構實作計畫文件 (docs/mqtt-implementation-plan.md)。 3. 更新全域開發框架規範 (framework.md),納入 Go Gateway 與 EMQX 架構說明。 4. 重構 IoT 通訊處理規範 (iot-communication/SKILL.md),支援 HTTP 與 MQTT 雙軌管線。 5. 更新背景 API 規範 (api-rules.md) 與技能觸發規則 (skill-trigger.md) 以符合新架構。
7.2 KiB
7.2 KiB
Star Cloud MQTT 基礎架構實作計畫 (Phase 1)
本計畫旨在建立 Star Cloud 的高併發通訊基石,包含佈署 EMQX Broker、開發 Go MQTT Gateway,並建立與 Laravel 之間的 Redis 雙向異步橋接機制。
User Review Required
Important
雙向通訊架構:本計畫不只處理「機台 ➜ 雲端」的上報,也同時建立「雲端 ➜ 機台」的指令下發通道。後台管理員在按下「遠端出貨」按鈕時,Laravel 會將指令推入 Redis,由 Go Gateway 轉發至 EMQX,再即時送達機台 APP。
Warning
資源配額:Go Gateway 雖然輕量,但在 Docker 環境中仍建議設定
mem_limit避免極端情況下的資源爭搶。
系統架構總覽
機台 Android APP
│
├─ [上行] Publish ──→ EMQX ──→ Go Gateway ──→ Redis List (mqtt_incoming_jobs) ──→ Laravel mqtt:listen ──→ Job ──→ MySQL
│
└─ [下行] Subscribe ←── EMQX ←── Go Gateway ←── Redis List (mqtt_outgoing_commands) ←── Laravel MqttCommandService
Proposed Changes
1. 基礎設施佈署 (Infrastructure)
[MODIFY] compose.yaml
- 新增
emqx服務:- Image:
emqx/emqx:5.10.3(開源版最終穩定版,Apache 2.0 授權) - Ports:
1883:1883(MQTT),8083:8083(WebSocket),18083:18083(Dashboard) - 加入
sail網路 - 配置 Redis Auth 插件環境變數,指向
star-cloud-redis
- Image:
- 新增
mqtt-gateway服務:- 使用
mqtt-gateway/Dockerfile進行 Multi-stage build - 連接至
sail網路 - 依賴
emqx與redis - 環境變數從
.env讀取
- 使用
[MODIFY] .env
- 新增以下環境變數:
# MQTT / EMQX
MQTT_BROKER_HOST=emqx
MQTT_BROKER_PORT=1883
EMQX_DASHBOARD_PORT=18083
# Go Gateway
MQTT_GATEWAY_CLIENT_ID=star-cloud-gateway
MQTT_REDIS_HOST=star-cloud-redis
MQTT_REDIS_PORT=6379
2. Go MQTT Gateway 開發 (The Bridge)
[NEW] 完整目錄結構
mqtt-gateway/
├── Dockerfile ← Multi-stage build (builder + alpine)
├── go.mod
├── go.sum
├── main.go ← 進入點:初始化、訊號監聽、graceful shutdown
├── config/
│ └── config.go ← 讀取環境變數 (EMQX_ADDR, REDIS_ADDR 等)
├── internal/
│ ├── handler/
│ │ ├── heartbeat_handler.go ← 處理 machine/+/heartbeat
│ │ ├── error_handler.go ← 處理 machine/+/error
│ │ └── transaction_handler.go ← 處理 machine/+/transaction
│ └── bridge/
│ ├── redis_consumer.go ← [下行] BLPOP mqtt_outgoing_commands,轉發至 EMQX
│ └── redis_pusher.go ← [上行] RPUSH mqtt_incoming_jobs
[上行邏輯] 機台 ➜ 雲端
- 訂閱
machine/+/heartbeat,machine/+/error,machine/+/transaction。 - 從 Topic 路徑提取
serial_no。 - 包裝成
BridgePayload:{ "type": "heartbeat", "serial_no": "M-001", "payload": { "current_page": 1, "temperature": 25.5 }, "received_at": "2026-04-14T09:00:00+08:00" } - 執行
RPUSH mqtt_incoming_jobs {json}。
[下行邏輯] 雲端 ➜ 機台 (新增)
- Go Gateway 啟動一條 Goroutine,持續
BLPOP mqtt_outgoing_commands。 - 取得 JSON 後解析目標
serial_no與command內容。 - Publish 至
machine/{serial_no}/command(QoS 1)。{ "target": "M-001", "command": "dispense", "payload": { "slot_no": 5, "transaction_id": "T202604140001" }, "message_id": "MSG_123456789" }
[NEW] mqtt-gateway/Dockerfile
# Stage 1: Build
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o gateway .
# Stage 2: Run
FROM alpine:3.20
COPY --from=builder /app/gateway /usr/local/bin/gateway
CMD ["gateway"]
3. Laravel 端實作
[NEW] app/Console/Commands/ListenMqttQueue.php
- Artisan Command:
mqtt:listen - 使用
BLPOP mqtt_incoming_jobs阻塞式監聽 - 根據
type分派至對應的 Laravel Job:heartbeat➜ProcessHeartbeatJoberror➜ProcessMachineErrorJobtransaction➜ProcessTransactionJob
[NEW] app/Services/Machine/MqttCommandService.php
- 提供
sendCommand(string $serialNo, string $command, array $payload)方法 - 將指令 JSON 推入 Redis List
mqtt_outgoing_commands - 供 Controller 呼叫(例如後台管理員按下「遠端出貨」按鈕)
[NEW] app/Jobs/Machine/ (三個 Job)
ProcessHeartbeatJob.php: 更新last_heard_at、溫度、頁面碼ProcessMachineErrorJob.php: 寫入machine_logs,觸發告警通知ProcessTransactionJob.php: 更新庫存、建立交易紀錄
[MODIFY] MachineAuthController.php
- 在 B014 核發
api_token後,同步寫入 Redis:Redis::set("machine_auth:{$serial_no}", hash('sha256', $token)); - Token 更新/撤銷時,同步刪除 Redis 中的對應 Key
Architecture Decisions (Confirmed)
✅ EMQX 版本策略
- 統一鎖定:開發與正式環境皆使用
emqx/emqx:5.10.3(開源版最後一個穩定版本)。 - 選擇理由:EMQX 從 5.9.0 起合併為統一版本,6.x 改用 BSL 商業授權。
5.10.3是純開源 (Apache 2.0) 的最終穩定版,功能完整且免授權費用。
✅ 下行指令安全性(無需額外 OTP)
App 連線 MQTT 時已使用 api_token 作為密碼完成身份認證,因此 MQTT 連線本身即為認證通道。安全性由以下三層保障:
- 連線層:App 使用
serial_no+api_token連線 EMQX,未通過驗證的裝置無法訂閱任何 Topic。 - ACL 層:EMQX 存取控制確保只有 Go Gateway 能 Publish 到
machine/{id}/command,機台 App 無法偽造指令。 - 冪等性層:每條下行指令的 Payload 包含唯一的
message_id,App 端應記錄已執行的 ID,防止重複執行同一條指令。
Note
結論:上行用 Token 當密碼、下行用 ACL 當門禁、
message_id當防重複鎖。三層防護已足夠,不需要額外的 OTP 機制。
Verification Plan
1. 基礎設施驗證
- 執行
./vendor/bin/sail up -d - 造訪
http://localhost:18083確認 EMQX Dashboard 正常啟動 - 確認 Go Gateway Container 的 logs 顯示 "Connected to EMQX" 與 "Connected to Redis"
2. 上行通訊測試 (機台 ➜ 雲端)
- 使用 MQTTX 工具連接
localhost:1883 - 發送心跳 JSON 至
machine/TEST-001/heartbeat - 檢查 Laravel 日誌確認
mqtt:listen成功接收並分派 Job - 檢查 MySQL
machines表的last_heard_at是否更新
3. 下行通訊測試 (雲端 ➜ 機台)
- 在 MQTTX 訂閱
machine/TEST-001/command - 透過 Laravel Tinker 呼叫
MqttCommandService::sendCommand('TEST-001', 'reboot', []) - 確認 MQTTX 收到 reboot 指令的 JSON
4. 壓力測試
- 使用 Go Script 模擬 500 台機台同時發送心跳
- 監控 Redis List 長度與 Laravel Worker 的處理速率