From ae554ef1309e709193dc8aadda6ebc8ef066166e Mon Sep 17 00:00:00 2001 From: sky121113 Date: Wed, 22 Apr 2026 16:25:48 +0800 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=E9=87=8D=E6=A7=8B=E8=88=87=E6=B7=B1?= =?UTF-8?q?=E5=8C=96=20MQTT=20=E9=80=9A=E8=A8=8A=E8=A6=8F=E7=AF=84?= =?UTF-8?q?=E4=B8=A6=E5=84=AA=E5=8C=96=20API=20=E6=96=87=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 重構 config/api-docs.php,將 HTTP API 與 MQTT Topic 徹底解耦,建立清晰的協議分類。 2. 實作 api-docs.blade.php 雙頁籤 (HTTP/MQTT) 介面,優化「極簡奢華風」視覺呈現與互動體驗。 3. 詳細定義 MQTT 指令下發 Payload,包含遠端出貨、庫存同步、遠端找零及系統控制。 4. 新增 Heartbeat (心跳) 與 Transaction (交易) MQTT 主題規範,確保 APP 端有明確的對接基準。 5. 修正 Heartbeat Payload 參數,僅保留實際傳送的溫度與韌體版本,確保文件與現況對齊。 6. 移除 B012 (商品同步) 尚未實作的 PATCH 方法,統一只保留 GET 方法以維持系統一致性。 7. 同步更新 SKILL.md 技術規格文件,確保開發規範與實際程式碼邏輯完全對等。 --- .agents/skills/api-technical-specs/SKILL.md | 28 +- .../skills/mqtt-communication-specs/SKILL.md | 27 +- config/api-docs.php | 189 +++++++- resources/views/docs/api-docs.blade.php | 456 ++++++++++-------- routes/api.php | 2 +- 5 files changed, 450 insertions(+), 252 deletions(-) diff --git a/.agents/skills/api-technical-specs/SKILL.md b/.agents/skills/api-technical-specs/SKILL.md index 90dc0b0..e1e21f8 100644 --- a/.agents/skills/api-technical-specs/SKILL.md +++ b/.agents/skills/api-technical-specs/SKILL.md @@ -116,9 +116,9 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與 ### 3.5 B012: 商品配置與商品主檔同步 (Unified Sync) -用於機台端獲取目前所有可販售商品的詳細配置。App 端應依據呼叫的方法決定數據處理方式。 +用於機台端獲取目前所有可販售商品的詳細配置。 -- **URL**: GET|PATCH /api/v1/app/machine/products/B012 +- **URL**: GET /api/v1/app/machine/products/B012 - **Authentication**: Bearer Token (Header) - **Request Body:** 無 (由 Token 自動識別機台) @@ -262,30 +262,6 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與 --- -### 3.11 B055: 遠端指令出貨控制 (Remote Dispense / Force Open) -用於遠端手動驅動機台出貨。通常用於補償使用者、測試機台或客服協助開門的情景。 - -- **URL**: GET|PUT /api/v1/app/machine/dispense/B055 -- **Authentication**: Bearer Token (Header) -- **運作模式**: - - **GET (查詢)**:當收到 **MQTT 指令 (`force_dispense` 或 `compensation`)** 時呼叫。雲端會回傳待執行的貨道編號與指令 ID。 - - **PUT (回報)**:實體出貨完成後回饋結果,以便雲端將該指令標記為「已執行」。 - -- **Response Body (GET - 查詢階段):** - -| 參數 | 類型 | 說明 | 範例 | -| :--- | :--- | :--- | :--- | -| data | Array | 指令物件陣列 | [{"res1": "ID", "res2": "SlotNo"}] | - -- **Request Body (PUT - 回報階段):** - -| 參數 | 類型 | 必填 | 說明 | 範例 | -| :--- | :--- | :--- | :--- | :--- | -| id | String | 是 | 雲端下發的指令 ID | "99" | -| type | String | 是 | 出貨類型代碼 (0: 成功, 其他: 失敗) | "0" | -| stock | String | 是 | 出貨後的貨道剩餘數量 | "9" | - ---- ## 4. 已遷移至 MQTT 的端點索引 diff --git a/.agents/skills/mqtt-communication-specs/SKILL.md b/.agents/skills/mqtt-communication-specs/SKILL.md index eece7c5..b509886 100644 --- a/.agents/skills/mqtt-communication-specs/SKILL.md +++ b/.agents/skills/mqtt-communication-specs/SKILL.md @@ -198,24 +198,23 @@ Mqtt3Client client = MqttClient.builder() ```json { "command": "dispense", + "command_id": "20", "payload": { - "slot_no": 5, - "transaction_id": "T202604140001" - }, - "command_id": "MSG_123456789" + "new_stock": 6, + "old_stock": 7, + "slot_no": "1" + } } ``` **常用指令集:** - `reboot`: 遠端重啟機台系統。 - `reboot_card`: 遠端重啟讀卡機。 -- `checkout`: 遠端結帳/登出指令。 -- `lock`: 遠端鎖定機台(暫停服務)。 -- `unlock`: 遠端解鎖機台(恢復服務)。 -- `change`: 遠端修改數值(夾帶 amount 參數)。 -- `reload_config`: 重新下載參數 (B014)。 -- `reload_products`: 重新同步商品 (B012,適用於商品主檔、圖文變更)。 -- `update_inventory`: 遠端即時修改庫存 (直接夾帶數量更新 SQLite,取代 HTTP)。 -- `dispense`: 遠端出貨指令 (B055)。 +- `checkout`: 遠端結帳。 +- `lock`: 鎖定頁鎖定。 +- `unlock`: 鎖定頁解鎖。 +- `change`: 遠端找零(夾帶 amount 參數)。 +- `update_inventory`: 遠端即時修改庫存。 +- `dispense`: 遠端出貨指令。 **`update_inventory` Payload 範例:** ```json @@ -291,11 +290,11 @@ Mqtt3Client client = MqttClient.builder() ## 5. Redis 橋接注意事項 ### 5.1 Key 前綴一致性 -Laravel 的 Redis 驅動會自動在 Key 前加上前綴 (預設 `starcloud_database_`)。Go Gateway 不使用 Laravel Redis 客戶端,因此**必須在程式碼中手動加上此前綴**: +Laravel 的 Redis 驅動會自動在 Key 前加上前綴 (預設 `star_cloud_database_`)。Go Gateway 不使用 Laravel Redis 客戶端,因此**必須在程式碼中手動加上此前綴**: ```go // main.go -prefixedIncomingKey := "starcloud_database_" + cfg.IncomingQueueKey +prefixedIncomingKey := "star_cloud_database_" + cfg.IncomingQueueKey pusher := bridge.NewRedisPusher(rdb, prefixedIncomingKey) ``` diff --git a/config/api-docs.php b/config/api-docs.php index 14ac369..059f6c5 100644 --- a/config/api-docs.php +++ b/config/api-docs.php @@ -1,10 +1,11 @@ 'Star Cloud IoT API 說明文件', + 'title' => 'Star Cloud IoT 通訊協議', 'version' => 'v1.0.0', - 'description' => '此文件提供 Star Cloud 智能販賣機 IoT 端點通訊協議說明,供硬體端與前端開發者調研與串接使用。', - 'categories' => [ + 'description' => '此文件提供 Star Cloud 智能販賣機 IoT 端點通訊協議說明,包含主動拉取 (HTTP REST) 與被動接收 (MQTT) 雙軌通訊機制,供硬體端與前端開發者調研與串接使用。', + + 'http_apis' => [ [ 'name' => '機台核心通訊 (IoT Core)', 'apis' => [ @@ -231,9 +232,9 @@ return [ [ 'name' => 'B012: 商品配置與商品主檔同步 (Unified Sync)', 'slug' => 'b012-unified-sync', - 'method' => 'GET/PATCH', + 'method' => 'GET', 'path' => '/api/v1/app/machine/products/B012', - 'description' => '用於機台端獲取目前所有可販售商品的詳細配置。GET 為全量同步,PATCH 為增量更新。', + 'description' => '用於機台端獲取目前所有可販售商品的詳細配置 (全量同步)。', 'headers' => [ 'Authorization' => 'Bearer ', 'Content-Type' => 'application/json', @@ -351,7 +352,7 @@ return [ [ 'name' => 'B024: 取貨碼/通行碼驗證與消耗回報', 'slug' => 'b024-access-code', - 'method' => 'POST/PUT', + 'method' => 'POST', 'path' => '/api/v1/app/sell/access-code/B024', 'description' => '處理代碼取貨流程。POST 用於驗證碼有效性,PUT 用於回報出貨成功並消耗代碼。', 'headers' => [ @@ -388,7 +389,7 @@ return [ [ 'name' => 'B027: 贈品碼/優惠券驗證與消耗回報', 'slug' => 'b027-freebie-code', - 'method' => 'POST/PUT', + 'method' => 'POST', 'path' => '/api/v1/app/sell/free-gift/B027', 'description' => '處理贈品券與 0 元購活動。邏輯與 B024 相似但對象為行銷贈品。', 'headers' => [ @@ -412,27 +413,169 @@ return [ 'message' => 'Free gift verified' ], ], + ] + ] + ], + + 'mqtt_topics' => [ + [ + 'name' => '指令與通訊 (Commands & Flow)', + 'topics' => [ [ - 'name' => 'B055: 遠端指令出貨控制 (Remote Dispense)', - 'slug' => 'b055-remote-dispense', - 'method' => 'POST/PUT', - 'path' => '/api/v1/app/machine/dispense/B055', - 'description' => '遠端手動驅動機台出貨。POST 用於獲取待處理指令,PUT 用於回報出貨完成。', - 'headers' => [ - 'Authorization' => 'Bearer ', - 'Content-Type' => 'application/json', + 'name' => '機台心跳上報 (Heartbeat)', + 'slug' => 'mqtt-heartbeat', + 'action' => 'PUB', + 'topic' => 'machine/{serial_no}/heartbeat', + 'qos' => 0, + 'description' => '機台每分鐘固定上報的狀態大包裝。整合了溫度、韌體版本與機台系統級日誌。', + 'payload_parameters' => [ + 'temperature' => ['type' => 'number', 'description' => '目前機台溫度'], + 'firmware_version' => ['type' => 'string', 'description' => 'APP 韌體版本'], ], - 'parameters' => [ - 'id' => ['type' => 'string', 'description' => '指令 ID (PUT)'], - 'stock' => ['type' => 'string', 'description' => '剩餘庫存 (PUT)'], + 'payload_example' => [ + 'temperature' => 20.5, + 'firmware_version' => '2.1.6' ], - 'request' => [], - 'response' => [ - 'success' => true, - 'data' => [ - ['slot_no' => '1', 'order_id' => 'RE-123'] + ], + [ + 'name' => '交易生命週期上報 (Transaction)', + 'slug' => 'mqtt-transaction', + 'action' => 'PUB', + 'topic' => 'machine/{serial_no}/transaction', + 'qos' => 1, + 'description' => '整合交易生命週期的三個階段:交易建立 (create)、發票開立 (invoice) 以及出貨結果 (dispense)。', + 'payload_parameters' => [ + 'action' => ['type' => 'string', 'description' => '生命週期動作 (create, invoice, dispense)'], + 'payment_type' => ['type' => 'string', 'description' => '支付方式 (action 為 create 時必填)'], + 'amount' => ['type' => 'number', 'description' => '交易金額 (create/invoice 時使用)'], + 'slot_no' => ['type' => 'string', 'description' => '出貨貨道號碼'], + 'invoice_no' => ['type' => 'string', 'description' => '發票號碼 (action 為 invoice 時必填)'], + 'status' => ['type' => 'string', 'description' => '出貨結果狀態 (action 為 dispense 時必填,如 success, failed)'], + ], + 'payload_example' => [ + 'action' => 'create', + 'payment_type' => 'credit_card', + 'amount' => 25.0, + 'slot_no' => '3', + 'product_id' => '5' + ], + 'notes' => '備註:此端點雲端後台暫未實作對應邏輯,但 APP 端可先行按照此結構開發。' + ], + [ + 'name' => '指令下發:遠端出貨 (Dispense)', + 'slug' => 'mqtt-command-dispense', + 'action' => 'SUB', + 'topic' => 'machine/{serial_no}/command', + 'qos' => 1, + 'description' => '雲端主動下發「遠端出貨」指令。機台收到後應立即驅動馬達進行出貨,出貨完成或失敗後回報 ACK。', + 'payload_parameters' => [ + 'command' => ['type' => 'string', 'description' => '固定為 "dispense"'], + 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'], + 'payload' => ['type' => 'object', 'description' => '出貨參數'], + 'payload.slot_no' => ['type' => 'string', 'description' => '欲出貨的貨道號碼'], + 'payload.old_stock' => ['type' => 'integer', 'description' => '出貨前的庫存量'], + 'payload.new_stock' => ['type' => 'integer', 'description' => '出貨後應剩餘的庫存量 (樂觀扣減結果)'], + ], + 'payload_example' => [ + 'command' => 'dispense', + 'command_id' => '9988776655', + 'payload' => [ + 'slot_no' => '1', + 'old_stock' => 10, + 'new_stock' => 9 ] ], + ], + [ + 'name' => '指令下發:庫存同步 (Update Inventory)', + 'slug' => 'mqtt-command-update-inventory', + 'action' => 'SUB', + 'topic' => 'machine/{serial_no}/command', + 'qos' => 1, + 'description' => '雲端主動下發「庫存更新」指令 (如從後台修改庫存、效期時觸發)。', + 'payload_parameters' => [ + 'command' => ['type' => 'string', 'description' => '固定為 "update_inventory"'], + 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'], + 'payload' => ['type' => 'object', 'description' => '更新參數'], + 'payload.data' => ['type' => 'array', 'description' => '欲更新的貨道資料陣列'], + 'payload.data[].slot_no' => ['type' => 'string', 'description' => '貨道號碼'], + 'payload.data[].stock' => ['type' => 'integer', 'description' => '最新庫存量'], + 'payload.data[].expiry_date' => ['type' => 'string', 'description' => '最新效期 (YYYY-MM-DD)'], + 'payload.data[].batch_no' => ['type' => 'string', 'description' => '批號 (可為 null)'], + ], + 'payload_example' => [ + 'command' => 'update_inventory', + 'command_id' => '5566778899', + 'payload' => [ + 'data' => [ + [ + 'slot_no' => '1', + 'stock' => 10, + 'expiry_date' => '2026-12-31', + 'batch_no' => null + ] + ] + ] + ], + ], + [ + 'name' => '指令下發:遠端找零 (Change)', + 'slug' => 'mqtt-command-change', + 'action' => 'SUB', + 'topic' => 'machine/{serial_no}/command', + 'qos' => 1, + 'description' => '雲端主動下發「找零」指令。指示機台退還指定金額的現金。', + 'payload_parameters' => [ + 'command' => ['type' => 'string', 'description' => '固定為 "change"'], + 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'], + 'payload' => ['type' => 'object', 'description' => '找零參數'], + 'payload.amount' => ['type' => 'integer', 'description' => '欲找零/退款的金額'], + ], + 'payload_example' => [ + 'command' => 'change', + 'command_id' => '1122334455', + 'payload' => [ + 'amount' => 50 + ] + ], + ], + [ + 'name' => '指令下發:系統控制 (System Control)', + 'slug' => 'mqtt-command-system', + 'action' => 'SUB', + 'topic' => 'machine/{serial_no}/command', + 'qos' => 1, + 'description' => '雲端主動下發的系統級別控制指令。包含重啟、設備鎖定解鎖、購物車結帳等無額外參數的指令。', + 'payload_parameters' => [ + 'command' => ['type' => 'string', 'description' => '系統指令類型。支援:reboot (重啟機台), reboot_card (重啟刷卡機), checkout (購物車結帳), lock (設備鎖定), unlock (設備解鎖)'], + 'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'], + 'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'], + ], + 'payload_example' => [ + 'command' => 'reboot', + 'command_id' => '9999999999', + 'payload' => new \stdClass() + ], + ], + + [ + 'name' => '指令執行回報 (Machine to Cloud)', + 'slug' => 'mqtt-command-ack', + 'action' => 'PUB', + 'topic' => 'machine/{serial_no}/command/ack', + 'qos' => 1, + 'description' => '機台執行完雲端指令後的主動回報。此回報極為重要,雲端將依此決定交易是否完成或需進行庫存回滾。', + 'payload_parameters' => [ + 'command_id' => ['type' => 'string', 'description' => '對應下發時的指令 ID'], + 'result' => ['type' => 'string', 'description' => '執行結果,僅接受: success 或 failed'], + 'stock' => ['type' => 'integer', 'description' => '執行指令後的最終剩餘庫存 (選填,通常出貨指令需帶上)'], + 'message' => ['type' => 'string', 'description' => '額外的錯誤訊息 (選填)'], + ], + 'payload_example' => [ + 'command_id' => '12345', + 'result' => 'success', + 'stock' => 9 + ], ] ] ] diff --git a/resources/views/docs/api-docs.blade.php b/resources/views/docs/api-docs.blade.php index 162b4ae..eafccee 100644 --- a/resources/views/docs/api-docs.blade.php +++ b/resources/views/docs/api-docs.blade.php @@ -9,9 +9,7 @@ - + @vite(['resources/css/app.css', 'resources/js/app.js']) @@ -21,7 +19,6 @@ --color-luxury-deep: #f8fafc; --color-luxury-card: #ffffff; --color-accent: #0891b2; - /* Cyan-600 for light mode */ } body { @@ -51,25 +48,6 @@ max-width: 1400px; } - .method-badge { - padding: 4px 10px; - border-radius: 6px; - font-weight: 800; - font-size: 13px; - text-transform: uppercase; - letter-spacing: 0.05em; - } - - .method-post { - background: #0891b2; - color: #ffffff; - } - - .method-get { - background: #10b981; - color: #ffffff; - } - .param-table th { text-align: left; padding: 0.75rem 1rem; @@ -94,6 +72,7 @@ font-family: 'ui-monospace', monospace; font-size: 14px; line-height: 1.5; + overflow-x: auto; } .luxury-nav-section { @@ -138,7 +117,6 @@ opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); @@ -147,39 +125,78 @@ - + -