第三方程式串接 (API Key 管理)
配發 API Key 與 Secret 給旅館 PMS 系統,供其換取 JWT Token 以控制硬體。
| 項次 | 合作夥伴名稱 | 國別 | API Key | 建立時間 | Firebase 綁定信箱 | AI 智慧助理 | 到期時間 | 狀態 | 一鍵同步 | 操作 |
|---|---|---|---|---|---|---|---|---|---|---|
| {{ (partnerPage - 1) * partnerPageSize + index + 1 }} | {{ formatPartnerId(partner.id) }}{{ partner.name }} | {{ getCountryFlag(partner.country || '台灣') }} {{ partner.country || '台灣' }} | {{ new Date(partner.created_at).toLocaleString('zh-TW', { year: 'numeric', month: 'numeric', day: 'numeric' }) }} |
{{ partner.user_email }}
尚未綁定
|
已啟用
未啟用
|
{{ partner.expires_at ? new Date(partner.expires_at).toLocaleString('zh-TW', { year: 'numeric', month: 'numeric', day: 'numeric' }) : '無期限' }}
|
{{ partner.is_active && !isExpired(partner.expires_at) ? '啟用中' : '已過期/停用' }} |
|
|
📊 API 呼叫統計分析
即時監控合作夥伴與系統 API 呼叫的健全度、頻率與異常統計
{{ statsData.kpis?.totalCalls?.toLocaleString() || 0 }}
{{ statsData.kpis?.successRate || '0.00' }}%
{{ statsData.kpis?.errorCalls?.toLocaleString() || 0 }}
{{ statsData.kpis?.topEndpoint || 'N/A' }}
📈 呼叫量時序趨勢
呈現統計區間內的總調用次數與成功率波動🍩 響應狀態碼分佈
分析請求回傳 HTTP 狀態碼之健康分佈比例🔥 熱門 API 呼叫排行 (Top 5)
| 方法 | API 端點 | 調用次數 |
|---|---|---|
| {{ ep.method }} | {{ ep.endpoint }} | {{ ep.count.toLocaleString() }} 次 |
| 無調用數據 | ||
📅 數據明細列表
| 時間區間 | 總呼叫 | 成功 | 錯誤 | 成功率 |
|---|---|---|---|---|
| {{ row.label }} | {{ row.total.toLocaleString() }} | {{ row.success.toLocaleString() }} | {{ row.error.toLocaleString() }} | {{ row.total > 0 ? ((row.success / row.total) * 100).toFixed(2) : '0.00' }}% |
| 無調用數據 | ||||
| 時間 | 合作夥伴 | 方法 | 端點 | 狀態 | 操作 |
|---|---|---|---|---|---|
| {{ formatLogTime(log.called_at) }} | {{ log.partner_name }} | {{ log.method }} | {{ log.status_code }} | ||
| 無詳細呼叫日誌數據 | |||||
🌿 碳盤查儀表板
每一張虛擬票券,都是一張減少的塑膠卡片。追蹤累計的碳足跡節省量。
{{ (carbonStats.total_cards_replaced || 0).toLocaleString() }}
張{{ formatCarbonKg(carbonStats.total_carbon_saved_g) }}
{{ carbonStats.total_carbon_saved_g >= 1000 ? 'kg' : 'g' }}{{ (carbonStats.equivalent?.trees_planted_year || 0).toLocaleString() }}
棵(一年吸碳量){{ (carbonStats.equivalent?.km_not_driven || 0).toLocaleString() }}
公里🍩 Apple / Google 比例
虛擬票券類型分佈📈 每月節省趨勢
按月累計的虛擬票券與碳節省量🏆 廠商碳貢獻排行
| 名次 | 合作夥伴 | 虛擬票券數 | 節省 CO₂e |
|---|---|---|---|
| {{ idx === 0 ? '🥇' : idx === 1 ? '🥈' : idx === 2 ? '🥉' : '#' + (idx + 1) }} | {{ p.partner_name || '未知廠商' }} | {{ p.cards.toLocaleString() }} 張 | {{ formatCarbonG(p.carbon_g) }} |
| 尚無碳盤查記錄 | |||
📅 月度碳節省明細
| 月份 | 票券數 | 節省 CO₂e |
|---|---|---|
| {{ m.month }} | {{ m.cards.toLocaleString() }} | {{ formatCarbonG(m.carbon_g) }} |
| 尚無碳盤查記錄 | ||
參數設定
🔗 左右帳號一鍵自動綁定分析說明
詳細解析 Cell API Manager (左側後台) 與 Cellbedell 用戶前台 (右側 Firebase) 之間的安全綁定與一鍵同步機制。
🔗 左右系統一鍵自動帳號綁定技術白皮書
🛡️ 100% 既有系統相容與安全保障 (Backward Compatibility)
- 零破壞性更新:資料庫新增之
firebase_uid與user_email欄位皆為Nullable欄位,現有廠商資料庫結構不受影響,既有授權資料依然為空,完美相容。 - 既有 API Key 驗證無感:原先所有廠商配發的 API Key、手機 App 以及硬體設備的 API 呼叫均不受干擾,100% 保持原本功能運行。
- 優雅的斷網與無金鑰降級機制:若系統未於「參數管理」配置 Firebase 私鑰,一鍵同步按鈕點擊後將會彈出溫馨引導,絕對不會引發後端服務 crash 崩潰。
🏗️ 一鍵綁定即時資料流架構 (Realtime Data Flow)
下圖展示了管理員在左側管理系統點擊「⚡ 一鍵同步」時,API Key 在左側 Node.js 後端、Firebase 實時資料庫與右側前台系統之間的實時流向:
graph TD
A["PMS 管理後台
左側系統"] -->|1. 填入 Firebase UID| B("點擊 ⚡ 一鍵同步")
B -->|2. 發送安全 POST 請求| C["Node.js 後端伺服器"]
C -->|3. 讀取廠商 API Key
pk_xxxxxxxx| D["Firebase Admin SDK"]
D -->|4. 使用私鑰建立連線| E[("Firebase RTDB
雲端資料庫")]
E -->|5. 寫入路徑| F["UID/host_user_account
/api_token"]
F -->|6. 即時資料監聽| G["Cellbedell 費用前台
右側系統"]
G -->|7. 讀取 API Key 呼叫 API| H["完美呈現 🔗 左右資料綁定"]
style A fill:#e0f2fe,stroke:#0ea5e9,stroke-width:2px
style C fill:#f3e8ff,stroke:#a855f7,stroke-width:2px
style E fill:#fff1f2,stroke:#f43f5e,stroke-width:2px
style G fill:#f0fdf4,stroke:#22c55e,stroke-width:2px
⚙️ 核心技術機制與安全性 (Security & Core Design)
🔑 API Key 直接同步設計
一鍵同步會將廠商的 API Key (pk_xxxxxxxx) 直接寫入到 Firebase RTDB。前端取得此 API Key 後可直接用於呼叫後端 API,無需額外簽發或轉換,簡單直覺且永不過期(除非管理員手動停用)。
🔑 憑證安全隔離 (Private Key Isolation)
Firebase Admin SDK 私鑰安全地保存在 Cell API Manager 的後端設定檔 wallet_config.json 中,完全對前台以及瀏覽器端隔離。只有在管理員手動發起「⚡ 一鍵同步」時,後端才會使用此憑證透過安全通道寫入 Firebase,極高安全性。
💻 實時同步監聽代碼範例 (Client-side Integration Example)
右側 Cellbedell 網頁前台使用 Firebase Web SDK 監聽該 UID 的 host_user_account/api_token 節點。一旦左側管理後台點擊同步寫入,前台即可實時零延遲觸發監聽並完成綁定與 API Key 帶入:
// 1. 在 Vue / React 中動態訂閱 Firebase RTDB 中該登入用戶的 API Key 節點
const userUid = firebase.auth().currentUser.uid;
const tokenRef = firebase.database().ref(`${userUid}/host_user_account/api_token`);
tokenRef.on('value', async (snapshot) => {
const apiKey = snapshot.val();
if (apiKey) {
console.log("⚡ 偵測到一鍵綁定 API Key 變更:", apiKey);
// 2. 將 API Key 設定至本機儲存空間或狀態管理中
localStorage.setItem('api_key', apiKey);
// 3. 使用 API Key 呼叫左側後台 API,帶入廠商即時授權合約與剩餘天數!
try {
const profileResponse = await fetch('https://pms-api.cellbedell.com/api/partner/profile', {
headers: {
'X-API-Key': apiKey
}
});
const profileData = await profileResponse.json();
// 4. 即時更新畫面狀態!
this.partnerProfile = profileData;
this.isLinked = true;
this.showSuccessToast("🎉 帳號已自動同步綁定!已帶入您的真實授權合約。");
} catch (err) {
console.error("資料帶入失敗:", err);
}
} else {
// 未綁定時,優雅降級為本置 Mock 模擬數據模式
this.isLinked = false;
console.log("📌 尚未綁定帳號,啟用本地端模擬合約資料。");
}
});
API 串接測試文件 - PMS 發卡
提供給第三方廠商的發卡硬體控制標準介接說明。
PMS MQTT 橋接 API 文件
📌 串接前置說明 (Integration Guide)
範例:
https://您的主機網域.com
👉 以「取得存取權杖」為例,完整網址為:
https://您的主機網域.com/api/auth/token
- 取得金鑰: 請先於合作夥伴管理列表,由管理員建立並配發一組專屬的
API Key與API Secret。 - 換取 Token: 依照下方【第 1 步】,使用這組金鑰換取 1 小時效期的
Access Token。 - 發送指令: 依照下方【第 2 步】,在 Request Header 中帶入
Bearer <Token>即可成功傳遞硬體控制資料。
1. 取得存取權杖 (Get Access Token)
在呼叫任何硬體控制 API 之前,必須先使用您的
API Key 與 API Secret 換取 1 小時效期的 JWT Token。
Request Body (application/json)
{
"api_key": "pk_160035...",
"api_secret": "sk_9f1c8a..."
}
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsIn...",
"token_type": "Bearer",
"expires_in": 3600
}
2. 發送硬體指令 (Publish Command)
使用取得的 Token 來發送控制指令給指定硬體設備。
Headers
Authorization: Bearer <您的_ACCESS_TOKEN>
Content-Type: application/json
Request Body (application/json)
{
"data": "置入取得的虛擬金鑰Vkey"
}
Response (成功)
{
"status": "success"
}
系統架構說明
PMS MQTT 橋接系統的完整資料流與元件架構圖。
PMS MQTT 橋接系統架構
📋 架構總覽
本系統透過 AWS 雲端服務實現 雙向 MQTT 通訊,讓第三方 PMS 廠商能安全地控制旅館硬體設備(如門鎖、發卡機)。
系統包含 下行控制(雲端→設備)與 上行回報(設備→雲端)兩條資料流路徑。
🧩 系統元件說明
| 元件 | 技術 | 說明 |
|---|---|---|
| PMS_MQTT 前端 | Vue 3 + Firebase |
環境監控儀表板,提供即時數據顯示、MQTT 指令發佈、系統日誌 |
| Cell API Manager | Express + SQLite + JWT |
廠商金鑰管理、JWT 認證、API 文件入口 (本系統) |
| AWS API Gateway | HTTP API |
接收前端 HTTP POST 請求,觸發 Lambda 函數 |
| PublishToMQTT | Lambda (Node.js 24.x) |
將 HTTP 請求轉換為 MQTT 訊息,發佈至 AWS IoT Core |
| AWS IoT Core | MQTT Broker |
雲端 MQTT 中樞,負責訊息路由與設備通訊 |
| IoT-To-Firestore | Lambda (Node.js 20.x) |
接收設備 MQTT 回報,寫入 Firebase Firestore 供前端即時更新 |
| Firebase Firestore | NoSQL 即時資料庫 |
儲存設備狀態與歷史紀錄,透過 onSnapshot 推播即時更新 |
📊 資料流架構圖
app.js
POST JSON
us-east-1
觸發
PublishToMQTT
MQTT Publish
MQTT Broker
訂閱接收
門鎖/發卡機
狀態回報
MQTT Publish
MQTT Broker
Rule 觸發
IoT-To-Firestore
寫入
即時資料庫
onSnapshot
即時更新 UI
🖥️ 本地服務對照表
| 服務 | Port | 專案 | 說明 |
|---|---|---|---|
| API Manager | localhost:3000 |
PMS_API_Manager | 後端核心 — 廠商管理、JWT 認證、硬體指令轉發 |
| Swagger Docs | localhost:4000 |
PMS_API_Docs | 互動式 API 文件 — 供開發者線上試打 API |
| MQTT Dashboard | localhost:8080 |
PMS_MQTT | 環境監控前端 — 即時數據、指令發佈、日誌 |
🔐 安全認證機制
- 管理員在 合作夥伴管理 頁面建立廠商,系統自動配發
API Key+API Secret - API Secret 經 SHA-256 雜湊 後儲存,原始值僅在建立時顯示一次
- 第三方使用 Key + Secret 呼叫
/api/auth/token換取 JWT Token(效期 1 小時) - 後續所有硬體控制 API 需在 Header 攜帶
Bearer Token,由 verifyToken 中介軟體 驗證 - Token 過期或廠商授權到期,系統自動拒絕存取(
403 Forbidden)
API儲存與效能
針對第三方呼叫所規劃的混合式儲存與高吞吐、低延遲架構優化(AWS DynamoDB 方案 B)。
⚡ API 儲存與效能 (AWS DynamoDB 方案 B)
為確保第三方廠商呼叫 API 反應最快、不塞車,系統針對統計日誌與大數據吞吐量進行了 混合式儲存架構 (Hybrid Architecture) 優化,並已全面完成實作與部署:
🌐 1. 關係型與非關係型雙庫混合 (Hybrid Engine)
- 輕量關係數據(金鑰管理、廠商設定、認證狀態): 存放在 SQLite (本地端) 或 AWS RDS (生產端) 中,便於進行精準管理與複雜交易。
- 海量日誌數據(API 呼叫日誌、JSON Payload、分析指標): 儲存在高吞吐的 AWS DynamoDB,徹底移出 SQL 資料庫。
⚡ 2. 零延遲異步背景日誌寫入 (Fire-and-Forget Logging)
在全域日誌攔截器中,寫入 DynamoDB 採用異步非阻塞方式。廠商在呼叫 API 時會以毫秒級速度 立刻收到結果,日誌寫入隨後於背景默默完成,API 呼叫反應時間增加 0 毫秒。
📊 3. 預聚合原子統計 (Atomic Stats Engine)
後台分析採用預聚合統計表 pms_api_stats。每次呼叫完成,後端自動在背景對該日/月/年份的計數器累加 +1。當管理員查看圖表時,後端無須 Scan 掃描數百萬筆詳細日誌,而是 5ms 瞬間讀取預聚合計數,圖表渲染快如閃電,且完全不塞車!
🛡️ 4. 多重容錯與本地降級機制 (SQLite Fallback)
若因網路波動或 AWS 區域性異常導致無法寫入 DynamoDB 時,系統具備自動容錯機制,會 無縫降級 (Graceful Fallback) 改寫入本地 SQLite 暫存,確保任何情況下服務皆不中斷,並在網路恢復後自動同步。
🔒 5. 雙環境資料隔離與生產安全防呆 (Dual-Environment Isolation & Seeding Guard)
- 本機與雲端物理隔離: 本地開發環境 (
localhost:3000) 的所有測試數據與日誌皆獨立儲存於本機 SQLite,與 AWS 生產環境完全物理隔離,確保本地端 any 開發與測試 100% 不污染線上營運資料。 - 生產環境自動防呆保護: 系統具備主動路徑與特徵識別機制。當於 AWS 生產環境 (
/home/ubuntu/...) 啟動時,系統將 自動且永久停用歷史資料自動填充功能 (Mock Seeding Guard)。這確保了雲端資料庫在被管理員清空後,重開機也絕不會在線上重新生成測試用假資料,保證線上分析數據的 100% 真實與純淨。
⚙️ 6. 系統管理與廠商呼叫徹底分離統計 (Billing-Safe Metric Isolation)
為確保合作廠商的 計費呼叫數據純淨、不可抵賴與具備法律效力,系統建立了獨立的分流計量引擎:
- 系統與管理員呼叫分類: 所有針對
/api/admin/...等管理路徑的請求,日誌自動歸入partner_id = 'system'(系統管理員),從源頭上與合作廠商的 API 呼叫切離。 - 聚合統計完美排除: 全域預聚合引擎 (
pms_api_stats) 與 SQLite 查詢在統計'all'(所有呼叫) 時,會 自動過濾與剔除'system'的任何呼叫記錄。 - 視覺化計費安全警示: 統計分析界面全面配備毛玻璃計費標籤,即時揭示目前統計範圍是否作為計費基底(🛡️ 應計費廠商呼叫 vs ⚠️ 免計費系統管理呼叫),確保營運財務帳單的百分之百乾淨與透明。
金流與擴充架構
未來第三方金流整合與 API Manager 自動化授權架構規劃。
為何金流不該分拆為獨立伺服器?
在商業架構中,強烈建議將「金流串接」與現有的「API Manager」整併在同一個伺服器 (Node.js) 中運行。
由於 API Key 與過期時間 (Expiry Date) 的資料庫 都存放在此 API Manager 內,若將金流系統獨立為另一台伺服器,將會大幅增加兩台伺服器互相通訊失敗的風險(例如客戶已付款,但金流伺服器跨網域通知 API Manager 失敗,導致客戶權限未開通,產生客服爭議)。
🔄 自動化金流運作流程 (SaaS 訂閱模式)
- 廠商發起付款: 廠商登入本儀表板,點擊「續約 / 購買方案」,系統引導至第三方支付 (如 ECPay 綠界, Stripe, 藍新) 刷卡頁面。
- 背景接收回呼 (Webhook): 廠商付款成功後,第三方支付會在背景發送一筆「成功付款通知 (Webhook POST)」給您的 API Manager 伺服器 (
/api/payment/webhook)。 - 自動展延授權: API Manager 接收並驗證 Webhook 的檢查碼後,直接執行資料庫更新:
UPDATE partners SET expiry_date = '+1 year' WHERE id = ? - 無縫恢復連線: 廠商的 API Key 瞬間恢復效力,自動取得全新的 JWT Token,無須人工介入即可繼續發送指令至 AWS。
🏗️ 綠界科技 (ECPay) 金流與 API 自動化展延系統架構圖
本系統已完整設計並實作第三方支付(綠界科技 ECPay)之自動化金流交易與金鑰延展流程。以下為該系統跨專案(費用前台 Checkout UI、API Manager 後端、Firebase 實時資料庫、獨立帳單儀表板)之核心動態資料流與簽章驗證時序圖:
sequenceDiagram
autonumber
actor User as 廠商客戶 (User)
participant Vue as 費用前台 Checkout UI (vue.test)
participant Backend as Cell API Manager (localhost:3000)
participant SQLite as SQLite 資料庫 (pms.db)
participant Firebase as Firebase RTDB (雲端)
participant Dash as 帳單儀表板 Billing UI (file://)
User->>Vue: 點擊 "Pay via ECPay" 升級/續約方案
Vue->>Backend: 發送 POST /api/payment/ecpay (apiKey, plan)
Note over Backend: 隨機生成唯一 MerchantTradeNo
以 HashKey/IV 進行 SHA256 簽章 (CheckMacValue)
Backend-->>Vue: 回傳完整簽章參數與 ECPay 串接端點
Vue->>User: 自動 Submit 表單導向 綠界測試刷卡頁面
Note over User: 於綠界測試環境付款完成,或
在測試模式使用「一鍵自動付款」Bypass 自動測試
User->>Backend: POST /api/payment/simulate-callback (模擬成功付款 Webhook)
Note over Backend: 驗證綠界回傳 CheckMacValue 簽章合法性
計算展延時間:現有到期日或當前時間 + 30 天
Backend->>SQLite: 執行資料庫展延:UPDATE partners SET expires_at = ? WHERE api_key = ?
Backend->>Firebase: 同步 expires_at 並將 Lockquantity/quantity 更新為對應額度
Backend-->>User: 導向交易成功畫面 / 即時提示
Dash->>Backend: 定時或手動觸發 GET /api/payment/orders 同步
Backend-->>Dash: 回傳 SQLite 內最新廠商到期日與訂單記錄
Note over Dash: 帳單儀表板即時更新到期日與續約圓餅圖!
🔑 綠界科技 Sandbox 介接資訊 (已配置於後端)
- 特店代號 (MerchantID):
2000132 - 金鑰 HashKey:
5294y06JbISpM5x9 - 向量 HashIV:
v77hoKGq4kWxNNIS - 付款介接端點 (Stage URL):
https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5
雙平台金流架構對比
B2B SaaS 訂閱系統(Herd)與 B2C 旅客訂房平台(HOTEL_OTA)之金流串接模式對比。
雙平台金流串接模式比較表
為了達到「敏感憑證統一控管」與「商業邏輯解耦」的目的,本系統(Cell API Manager)被設計為集中式金流中台。 以下是 B2B SaaS 訂閱模式(Herd)與 B2C 旅客訂房模式(HOTEL_OTA)在金流架構設計上的主要差異:
| 比較維度 | B2B SaaS 訂閱系統 (Herd/vue) | B2C 旅客訂房平台 (HOTEL_OTA) |
|---|---|---|
| 業務定位 | SaaS 服務訂閱、硬體授權額度展延與金鑰有效期限計算。 | 消費者特定房源在指定日期的預訂(Transactional Booking)。 |
| 發起者與付費對象 | 系統合作夥伴 (Partner)。 | 平台註冊旅客 (Guest)。 |
| 前端金流 API 呼叫 | 直接呼叫中台 API POST /api/payment/ecpay。 |
呼叫 Firebase Callable Function createBooking,再由 Firebase 後端中轉呼叫金流中台。 |
| 敏感金鑰儲存 | 儲存於 PMS_API_Manager (中台),安全無洩漏風險。 |
儲存於 PMS_API_Manager (中台),HOTEL_OTA 專案不保存任何綠界私鑰,透過雙向 Secret 來互相驗證。 |
| 交易防重疊機制 | 無特殊要求,直接依付款累加訂閱效期。 | 在向中台開單前,必須透過 Firestore 先將房源日曆鎖定為 pending,防止併發重疊預訂。 |
| 付款完成確認流程 | 中台直接更新本地 SQLite 資料庫與同步雲端並完成展延。 | 中台在背景接收到綠界 Webhook 後,使用 Secret 發送 Webhook 請求通知 HOTEL_OTA 的 confirmBooking webhook 接口,將訂單標記為 confirmed。 |
🏗️ 金流架構流程圖 (B2B vs B2C)
1. B2B 系統金鑰訂閱展延流 (Herd/vue)
sequenceDiagram
autonumber
actor Partner as 合作夥伴 (Partner)
participant Vue as 費用前台 Checkout UI
participant Backend as Cell API Manager (中台)
participant ECPay as 綠界金流
Partner->>Vue: 點擊 "Pay via ECPay" 續約方案
Vue->>Backend: POST /api/payment/ecpay (apiKey, plan, months)
Note over Backend: 使用中台內置 ECPay 金鑰進行簽章
生成 CheckMacValue
Backend-->>Vue: 回傳完整簽章參數與 ECPay URL
Vue->>ECPay: Form Submit (以 _blank 另開新分頁)
ECPay-->>Backend: 背景發送 Webhook 成功通知 (ReturnURL)
Backend->>Backend: 更新本地資料庫並延長該 Partner 效期
2. B2C 旅客訂房金流流 (HOTEL_OTA)
sequenceDiagram
autonumber
actor Guest as 旅客 (Guest)
participant Front as HOTEL_OTA 前端
participant Firebase as Cloud Functions (createBooking)
participant Backend as Cell API Manager (中台)
participant ECPay as 綠界金流
Guest->>Front: 選擇房源與日期,點擊「預訂並付款」
Front->>Firebase: 呼叫 Callable 建立預訂
Note over Firebase: 寫入 Firestore 建立 pending 訂單
防併發鎖定房源日曆 (Calendar)
Firebase->>Backend: POST /api/payment/ecpay (帶入 productType="hotel_booking", secret, amount, bookingId)
Note over Backend: 驗證 Secret 安全性,生成綠界交易編號
使用中台 ECPay 金鑰簽章
Backend-->>Firebase: 回傳簽章參數與支付 URL
Firebase-->>Front: 回傳金流參數至前端
Front->>ECPay: Form Submit (在當前視窗轉跳)
ECPay-->>Backend: 付款成功 -> 背景發送 Webhook (ReturnURL)
Note over Backend: 驗證 CheckMacValue 簽章合法性
識別為 hotel_booking 商品
Backend->>Firebase: POST /confirmBooking (帶入 x-payment-secret Header, bookingId)
Note over Firebase: 驗證 Secret,更新 Firestore 該訂單為 confirmed
💡 「集中式金流中台」設計的優勢分析
- 憑證安全隔離 (Security Boundary): 敏感金鑰 (ECPay Key/IV) 僅集中保存在 API Manager 中。子系統 (HOTEL_OTA) 就算被駭也拿不到付費憑證。
- 減少維護成本 (Single Point of Maintenance): 如果未來綠界的規格升級(例如加密演算法變更)或是更換成 Stripe、LinePay 等,只需要在中台更新,前端與子系統不需要重複編寫繁瑣的金流簽章演算法。
- 系統間鬆耦合 (Loose Coupling): 兩個平台共享同一個金流接口,並以標準的商品類型 (
productType) 來決定不同的付款確認邏輯,既保持高複用度,又互不干擾。
Hotel & BnB 收費分析
針對 HOTEL_OTA 平台未來收費策略的商業模式與架構規劃分析建議。
一、兩大主流收費模式深度對比
在訂房平台(OTA)與旅宿管理系統(PMS)市場中,主要有「交易手續費抽成」與「固定訂閱制」兩種營利模式。以下是詳細對比分析:
| 評估維度 | 模式 A:預訂交易抽成 (Airbnb 模式) | 模式 B:固定訂閱制 (SaaS / PMS 模式) |
|---|---|---|
| 運作機制 | 針對每筆預訂付款進行百分比抽成(例如抽 5% - 15%)。 | 房東每月或每年支付固定年/月費,不論成交量多寡都不加收費用。 |
| 房東開發難度 | 極低(零進入門檻) 房東沒有成交就無須付費,上架意願極高。 |
較高(有初期門檻) 在沒有訂單保障前,新房東不願意預先支付月費。 |
| 營收天花板 | 極高 隨著平台總交易金額(GMV)成長,平台獲利呈指數級上升。 |
有上限 僅取決於房東訂閱戶數,無法共享房東生意做大時的紅利。 |
| 雙方利益綁定 | 高度綁定 平台會主動優化 SEO、投放廣告,因為「房東成交 = 平台賺錢」。 |
低綁定 平台偏向純提供軟體工具,不負責為房東導流與推廣。 |
| 跳過交易風險 | 中等至偏高。房東與熟客常試圖私下交易以規避手續費。 | 極低。既然已付固定月費,房東會希望盡量走平台以方便帳務管理。 |
二、Airbnb 實際收費標準參考
Airbnb 主要採用「拆分服務費 (Split-fee)」機制:
- 房東端 (Host Fee): 抽取每筆訂單的 3%(主要用作信用卡金流處理成本)。
- 旅客端 (Guest Fee): 抽取每筆訂單的 14% 左右,這是平台的主要營收來源。
- 特殊模式(單一費率): 針對連鎖飯店或使用軟體系統串接的專業房客,亦提供「僅抽房東 15%,不抽旅客」的模式。
三、 未來收費模式策略建議
針對 HOTEL_OTA 平台,若純收訂閱制會極難開發前期房東;純收抽成制則對高營業額的專業房東缺乏吸引力。 因此,強烈建議採用「混合型階梯收費模式 (Hybrid Tiered Model)」:
免月費,按預訂抽成
- 房東零上架成本
- 有成交平台才抽成
- 適合副業房東、閒置房源者
低月費,低預訂抽成
- 降低高營業額下的抽成負擔
- 3% 僅包含綠界處理成本
- 適合高客單價、包棟民宿
生態系綁定免費方案
- 直接免收 HOTEL_OTA 抽成與月費
- 僅收取基本金流處理代收費 (3%)
- 適合導入自助機/物聯網飯店
部署與更新教學
交接文件:如何更新 AWS EC2 雲端伺服器的程式碼與架構資訊
📦 一鍵自動更新流程 (推薦)
為了方便交接與後續維護,系統已內建自動化部署腳本。未來若您在本地端 (Local) 修改了任何 PMS_API_Manager 的程式碼,只需透過以下步驟即可一鍵將更新推送到 AWS EC2 正式環境:
更新三部曲
- 打開您的 Mac 終端機 (Terminal)。
- 切換目錄到專案資料夾:
cd ~/Desktop/PMS_API_Manager - 執行自動化部署腳本:
./deploy.sh
deploy.sh 會自動透過 rsync 將您本地端的最新程式碼傳送到 EC2,並且 自動避開 data/pms.db 資料庫檔案(避免覆蓋雲端的正式廠商資料)。最後會透過 SSH 自動遠端執行 pm2 restart pms-api,讓新程式碼立即生效。
☁️ 雲端伺服器 (EC2) 規格與連線資訊
- 公網 IP 位址 (BASE URL):
http://3.27.15.192:3000 - SSH 連線使用者:
ubuntu - SSH 金鑰路徑 (本地):
~/AWS_Key/pms-api-key.pem - 專案存放路徑 (EC2):
~/PMS_API_Manager - 背景常駐工具:
PM2(進程名稱為pms-api)
手動連線至主機 (進階)
若需要手動進去主機查看日誌或除錯,請使用以下指令連線:
ssh -i ~/AWS_Key/pms-api-key.pem ubuntu@3.27.15.192
查看 PM2 即時運行日誌:
pm2 logs pms-api
Wallet 憑證 AWS 部署說明 (方案 B)
交接與規劃:在正式生產環境 (AWS EC2) 下使用 Secrets Manager 與 S3 的無硬碟殘留憑證部署方案。
AWS 生產環境憑證安全整合指南
🛡️ 方案 B 設計核心:零磁碟殘留 (Zero Disk Residual)
在生產環境中,將 .p12 簽章私鑰 以實體檔案形式儲存在 EC2 的硬碟中具有潛在風險(若伺服器被攻破,證書即刻外洩)。
方案 B 透過將憑證託管於 Amazon S3 (Private),密碼託管於 AWS Secrets Manager,並結合 EC2 的 IAM Role 角色授權,實現了「發卡瞬間從記憶體載入、簽章完畢即刻銷毀」的高安全性架構。
1. AWS 雲端安全發卡架構流
在 AWS 部署方案下,API Docs 伺服器與 AWS 託管服務的互動關係如下:
graph TD
PMS["1. 第三方 PMS 系統 - 呼叫 API"] -->|POST /api/wallet/generate| API["2. API 發卡伺服器 - AWS EC2"]
subgraph AWS["AWS 安全網路邊界 - IAM Role 授權"]
API -->|3. 動態下載 P12 憑證| S3["Amazon S3 私有加密儲存桶"]
API -->|4. 取得私鑰密碼| SM["AWS Secrets Manager 憑證密鑰服務"]
API -->|5. 記憶體載入簽署| MEM["RAM 記憶體簽章及打包"]
end
API -->|6. 回傳 pkpass 串流| PMS
2. AWS 權限配置 (IAM Policy)
您需要為執行 Node.js 的 AWS EC2 實例綁定一個 IAM Role (EC2 實例描述檔),並附加以下最低權限 Policy,限制其僅能存取特定的儲存桶與密鑰:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadP12FromS3",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::your-pms-wallet-credentials/pass_credential.p12"
},
{
"Sid": "AllowReadPasswordFromSecretsManager",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:pms/wallet/config-*"
}
]
}
3. AWS SDK 整合實作程式範例
在伺服器端,我們可以使用環境變數 WALLET_STORAGE_MODE=AWS 來動態切換憑證來源。以下為 Node.js 讀取 AWS 憑證的程式範例:
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const s3Client = new S3Client({ region: "us-east-1" });
const secretsClient = new SecretsManagerClient({ region: "us-east-1" });
/**
* 取得 Wallet 憑證與密碼 (支援本地/AWS雙模式)
*/
async function getWalletCredentials() {
if (process.env.WALLET_STORAGE_MODE === "AWS") {
console.log("正在從 AWS S3 與 Secrets Manager 獲取憑證...");
// 1. 從 S3 讀取 .p12 檔案
const s3Params = { Bucket: "your-pms-wallet-credentials", Key: "pass_credential.p12" };
const s3Response = await s3Client.send(new GetObjectCommand(s3Params));
// 將 Stream 轉為 Buffer
const streamToBuffer = (stream) =>
new Promise((resolve, reject) => {
const chunks = [];
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("error", reject);
stream.on("end", () => resolve(Buffer.concat(chunks)));
});
const p12Buffer = await streamToBuffer(s3Response.Body);
// 2. 從 Secrets Manager 讀取憑證密碼
const secretResponse = await secretsClient.send(
new GetSecretValueCommand({ SecretId: "pms/wallet/config" })
);
const secrets = JSON.parse(secretResponse.SecretString);
const p12Password = secrets.p12Password;
return { p12Buffer, p12Password };
} else {
// 本地模式 (Fallback)
const fs = require('fs');
const path = require('path');
const p12Path = path.resolve(__dirname, '../PMS_API_Manager/data/pass_credential.p12');
const configPath = path.resolve(__dirname, '../PMS_API_Manager/data/wallet_config.json');
let p12Buffer = fs.readFileSync(path.join(__dirname, 'resources/mock_credential.p12'));
let p12Password = 'mockpass';
if (fs.existsSync(p12Path)) {
p12Buffer = fs.readFileSync(p12Path);
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
p12Password = config.p12Password || '';
}
}
return { p12Buffer, p12Password };
}
}
4. 本地開發與 AWS 環境無縫切換
在本地測試時,您可以使用極簡的環境變數 .env 設定來使伺服器在 LOCAL 與 AWS 模式間流暢切換:
- 在本地測試時:不設定變數,後端預設自動採用 LOCAL 模式,直接讀取您在管理後台 (localhost:3000) 上傳之憑證,或自動 Fallback 使用
mock_credential.p12。 - 在部署 AWS 時:於 EC2 啟動指令中宣告
WALLET_STORAGE_MODE=AWS,Node.js 即會自動使用 AWS SDK 拉取密鑰,無需改動任何主程式,實現環境無痛轉移!
Wallet 票卡架構與生成流程說明
為 iOS Apple Wallet (.pkpass) 與 Android 提供發卡資料標準與離線/後端打包架構說明。
Wallet 票卡架構與生成流程說明
📌 票卡底層概念 (Basic Concepts)
當我們在 Web3 錢包或 PMS 發卡系統中生成一個票卡安裝檔時,不論是 iOS 還是 Android,都需要將虛擬金鑰 (Vkey) 或 ERC20 Presets 等核心資料透過條碼 (Barcode) 或 NFC 的方式嵌入至數位票卡中,以利硬體掃描讀取。
.pkpass,本質上是一個內含簽章與配置檔案的 ZIP 壓縮檔。iOS 原生系統會嚴格執行 Apple PKCS#7 證書鏈校驗。
.pkpass 包,通常跳過嚴格的簽章校驗)。
1. 票卡基礎資料結構 (.pkpass 包內檔案組成)
一個標準的 .pkpass 票卡解壓縮後,必須包含以下關鍵檔案:
| 檔案名稱 | 類型與必要性 | 說明與核心欄位 |
|---|---|---|
pass.json |
必要 (JSON) | 定義票卡類型 (如 generic)、外觀顏色、欄位文字與條碼編碼內容 (將 Vkey 寫入 barcodes.message 中)。 |
manifest.json |
必要 (JSON) | 清單檔。列出包內所有檔案(如圖檔、pass.json)及其對應的 SHA-1 Hash 值,用以防止竄改。 |
signature |
必要 (Binary) |
PKCS#7 二進位簽章。使用 Apple 簽發的 Pass Type ID 私鑰與證書對 manifest.json 進行雜湊簽章的結果。
|
icon.pnglogo.png |
非必要但建議 (PNG) | 票卡的視覺素材。包含 1x 圖片與高解析度的 @2x, @3x 版本(例如通知中心圖示、卡片左上角商標等)。 |
pass.json 範例內容:
{
"formatVersion": 1,
"passTypeIdentifier": "pass.com.pms.wallet.vkey",
"teamIdentifier": "TEAMID1234",
"organizationName": "PMS Group",
"serialNumber": "pms_vkey_888888",
"description": "PMS Web3 Hardware Access Key",
"generic": {
"primaryFields": [
{
"key": "vkey_name",
"label": "金鑰名稱",
"value": "Web3 開門晶片"
}
],
"secondaryFields": [
{
"key": "expire",
"label": "效期截止時間",
"value": "2026-12-31 23:59:59"
}
]
},
"barcodes": [
{
"format": "PKBarcodeFormatQR",
"message": "pms_vkey_data_payload_here_0x127391...",
"messageEncoding": "iso-8859-1",
"altText": "掃描以進行硬體開門驗證"
}
],
"backgroundColor": "rgb(15, 23, 42)",
"foregroundColor": "rgb(255, 255, 255)",
"labelColor": "rgb(148, 163, 184)"
}
2. iOS Wallet (.pkpass) 安裝檔製作流程
因為 iOS 系統原生對 Pass 的安全性校驗極高,所以在發卡伺服器上必須有一套標準的簽署流程來生成封裝包:
📊 iOS 票卡簽章與生成資料流
graph TD
A[1. 準備原始檔素材與 pass.json] --> B[2. 計算各檔案 SHA-1 生成 manifest.json]
B --> C[3. 讀取 Apple Pass 憑證與私鑰 .p12]
C --> D[4. 使用 OpenSSL 進行 PKCS#7 簽章生成 signature]
D --> E[5. 將所有檔案打包為 ZIP 並重新命名為 .pkpass]
E --> F[6. 使用者透過 iOS Safari / Mail / 簡訊下載安裝]
F --> G{iOS 內建 Wallet 校驗簽章}
G -- 驗證通過 --> H[成功匯入錢包並顯示卡片]
G -- 驗證失敗 --> I[提示「無法讀取此票卡/憑證無效」]
iOS 原生 Wallet App 對此為強校驗,若
signature 檔為空,或者簽章所使用的證書鏈不被 Apple Trust Store 信任,iOS 下載後會直接跳出「無法加入票卡」/「票卡無效」。
因此,若要在正式 iOS 裝置上部署,發卡方必須在 Apple Developer Account 註冊一個 Pass Type ID 並下載憑證。
👨💻 後端 (macOS / Linux) 使用 OpenSSL 自動簽署指令參考
在後端,我們可以用 Node.js 結合系統 native 的 openssl 快速抽離憑證私鑰並進行 manifest 簽章,免除使用笨重的第三方庫:
# 1. 從發卡方憑證 p12 中提取憑證 (Cert)
openssl pkcs12 -in pass_credential.p12 -clcerts -nokeys -out certificate.pem -passin pass:憑證密碼
# 2. 從 p12 中提取私鑰 (Private Key)
openssl pkcs12 -in pass_credential.p12 -nocerts -out privatekey.pem -passin pass:憑證密碼 -passout pass:私鑰本機密碼
# 3. 使用憑證與私鑰,對 manifest.json 進行 PKCS#7 簽章,輸出二進位 signature
openssl smime -binary -sign -certfile certificate.pem -signer certificate.pem -inkey privatekey.pem -in manifest.json -out signature -outform DER -passin pass:私鑰本機密碼
完成後,將 pass.json、圖片、manifest.json 與 signature 通通用 ZIP 打包並重新命名為 *.pkpass 即是一組合格的安裝檔。
3. Android Wallet 安裝檔製作流程
相較於 iOS,Android 在數位票卡的支援上更為多元與開放,可以分為兩種主流發卡途徑:
🛠️ 途徑 A:直接沿用 .pkpass 格式(相容第三方 Wallet App)
Android 系統原生沒有內建 PKPass 解析引擎,但使用者只需在 Google Play 安裝如 PassWallet 或 PassAndroid 等大牌軟體,便能直接開啟 .pkpass 檔案。
也就是說,只要您將
pass.json 的結構與靜態素材打包正確,即使 signature 檔是空的,或者使用自簽/Mock 的簽署,依然可以在 Android 手機上 100% 成功解析、成功渲染 QR Code Vkey 並正常安裝! 這對於開發初期與免付費憑證的使用場景極為友善。
🌐 途徑 B:Google Wallet 官方 REST API(整合 Google 帳戶)
這是最尊榮的官方整合方式。使用者不需安裝任何第三方 App,點擊網頁上的 「Add to Google Wallet」 按鈕,卡片便會同步儲存至使用者的 Google 帳戶並常駐於 Android 系統的 Google Wallet 中。
📊 Google Wallet 官方發卡資料流
graph TD
A[1. Google Wallet Console 建立帳號] --> B[2. 建立 Service Account 下載 JSON 金鑰]
B --> C[3. 於後端定義 PassClass 範本並發布至雲端]
C --> D[4. 後端依使用者資料/Vkey 生成動態 PassObject 並組裝為 JWT]
D --> E[5. 前端呈現 'Add to Google Wallet' 憑證超連結]
E --> F[6. 使用者點擊將卡片保存至 Google Wallet]
{
"iss": "pms-service-account@pms-iot-project.iam.gserviceaccount.com",
"aud": "google",
"typ": "savetowallet",
"origins": [],
"payload": {
"genericObjects": [
{
"id": "1234567890123456789.pms_vkey_888888",
"classId": "1234567890123456789.pms_vkey_class",
"genericType": "GENERIC_OTHER",
"cardTitle": {
"defaultValue": {
"language": "zh-TW",
"value": "PMS 門禁金鑰"
}
},
"header": {
"defaultValue": {
"language": "zh-TW",
"value": "已啟用"
}
},
"barcode": {
"type": "QR_CODE",
"value": "pms_vkey_data_payload_here_0x127391...",
"alternateText": "門禁專用 Vkey"
},
"hexBackgroundColor": "#0f172a"
}
]
}
}
🌿 碳盤查與計算方法說明
詳細說明以數位票卡 (Apple & Google Wallet) 替代傳統 PVC 實體卡片的減碳計算邏輯與換算標準。
碳盤查計算與生命週期評估 (LCA) 說明文件
♻️ 減碳核心理念
每一次成功的 Wallet Pass API 呼叫,代表產生一張虛擬票卡,並成功替代了一張實體塑膠卡片(如會員卡、識別證或門禁卡)的製造、印刷、寄送與廢棄過程。透過對這些 API 進行自動監控統計,本平台提供可被稽核的碳足跡減量統計數據,助力企業綠色轉型與 ESG 指標申報。
1. 實體卡片碳足跡標準(ISO 14064 LCA 評估)
根據國際 ISO 14064/14067 的生命週期評估 (LCA) 標準研究,一張標準 ISO 7810 規格的 PVC 塑膠卡片,在其生命週期中各階段所產生的碳足跡平均如下:
| 生命週期階段 | 碳排放量 (CO₂e) | 排放來源與說明 |
|---|---|---|
| 原料生產 (Raw Material) | 約 10g | 標準卡重 5g。PVC 原料生產碳排係數約為 2.0 kg CO₂e / kg PVC。 |
| 印刷製造 (Manufacturing) | 約 8g | 包含卡面全彩印刷、裁切、保護膜覆蓋、磁條或 RFID 晶片嵌入封裝。 |
| 包裝物流 (Logistics) | 約 5g | 包裝紙套/信封之生產,以及卡片寄送至持卡人手上的快遞或郵件物流碳排。 |
| 廢棄處置 (End of Life) | 約 3g | 因 PVC 不可自然分解,後續進入焚化爐或垃圾掩埋場所產生的溫室氣體。 |
| 每卡累計節量 | 26g CO₂e / 張 | 系統預設採用的基準值(管理員可於後台自訂調整)。 |
2. 等效環境貢獻轉換公式
為了將抽象的「碳公克數 (g CO₂e)」轉化為直觀易懂的綠色成效,系統使用以下經過科學驗證的換算基準:
總碳節省量 (g) ÷ 1,000 ÷ 20
基準:一棵健康成年樹木每年平均吸收約 20 kg (20,000g) 的 CO₂。
總碳節省量 (g) ÷ 1,000 ÷ 0.21
基準:中型汽油乘用車行駛每公里平均排放約 0.21 kg (210g) 的 CO₂。
總簽發票卡張數 × 5
基準:一張符合 ISO 標準之實體 PVC 卡片重量約為 5 公克。
3. 計量統計機制與 API 觸發
系統透過中介軟體 (Middleware) 即時捕捉並篩選代表「成功簽發虛擬票卡」的 API 調用,排除無效或一般管理請求:
- 計入之 API 端點:
POST /api/wallet/sign-pass— Apple Wallet 票卡簽署與發佈POST /api/wallet/sign-google-pass— Google Wallet 票卡簽署與發佈
- 統計判定條件: API 回傳狀態碼必須小於 400 (意即 HTTP 2xx 成功)。所有 HTTP 4xx (參數錯誤) 或 5xx (伺服器錯誤) 均不予計入。
- 儲存與防重機制: 每次觸發皆會在 SQLite / DynamoDB 雙軌資料庫中寫入以下結構:
{ partner_id: "apple_store_01", partner_name: "Apple Store", wallet_type: "apple", // "apple" 或 "google" endpoint: "/api/wallet/sign-pass", carbon_g: 26.0, // 寫入當下系統設定之碳係數 recorded_at: "2026-06-17 18:00:00" }
4. 歷史回補與自訂係數
系統考量到「事後調整碳係數」及「部署前歷史資料匯入」的需求,設計了以下彈性機制:
wallet_config.json),後續新發行票卡之計量將自動套用新係數。
api_logs 歷史 API 日誌,過濾出符合條件的成功簽發記錄,以「目前的碳係數」重新批量計算,補登回 carbon_stats 中,確保數據完整性。
AWS EC2 多應用部署架構
單一主機透過 Nginx 反向代理,以路徑節點掛載多個獨立應用服務。
架構設計理念
💡 為什麼用一台 EC2?
使用一台 EC2 搭配 Nginx 反向代理是目前最經濟且靈活的方案。
一個固定 IP (3.27.15.192),
可以透過「路徑節點」掛載無限多個應用,各應用之間完全獨立、互不干擾。
未來若流量增長,可隨時拆分為獨立主機或升級為 Load Balancer 架構。
📊 流量分流架構圖
PM2 → Port 3000靜態檔案服務PM2 → Port 8899靜態檔案服務📋 應用服務對照表
| 專案名稱 | 類型 | 部署方式 | URL 路徑 | 狀態 |
|---|---|---|---|---|
| Cell API Manager | Node.js API | PM2 (Port 3000) |
/pms/ |
運行中 |
| Vsitth 訪客管理 | 靜態前端 + Firebase | Nginx 靜態服務 |
/vsitth/ |
運行中 |
| Meeting System | 靜態前端 + Firebase | Nginx 靜態服務 |
/meeting/ |
運行中 |
| AI Cube 健康監測 | 靜態前端 | Nginx 靜態服務 |
/aicube/ |
運行中 |
| Blockchain Test | 靜態前端 | Nginx 靜態服務 |
/blockchain/ |
運行中 |
| 採購分析物料成本 | Node.js + SQLite | PM2 (Port 8899) |
/bom/ |
規劃中 |
🖥️ EC2 主機資訊
🧭 架構決策:一台 EC2 還是多台?
目前開發方向分為兩大類,性質不同但共用同一台 EC2:
| 🔌 API 串接管理 | 🖥️ 前後端應用開發 | |
|---|---|---|
| 代表專案 | Cell API Manager | Vsitth、BOM 採購分析、Meeting System |
| 性質 | 對外開放的 API 閘道,第三方廠商會打進來 | 內部/自用工具,瀏覽器直接操作 |
| 安全等級 | 🔴 較高 (JWT、API Key、廠商機密) | 🟢 一般 (Firebase Auth 驗證) |
| 流量來源 | 機器對機器 (M2M),24/7 自動化 | 人為操作,上班時間為主 |
✅ 現階段結論:使用一台 EC2 + Nginx 子路徑
- 成本最優:一台 EC2 (t3.micro) 約 $8-10 USD/月,兩台則翻倍
- 維護集中:一個地方做安全更新、看 Log、管理 SSH Key
- 隔離足夠:各應用跑在獨立 PM2 進程,互不影響。Nginx 路徑分流本身即邏輯隔離
- 天然支持擴展:未來若要拆分,只需把 Nginx 的 proxy_pass 指向新 IP,零改動
⚡ 什麼時候該拆成兩台 EC2?
| 觸發條件 | 說明 | ||
|---|---|---|---|
| 合作廠商數量 > 10+ | API 流量開始影響前端應用的回應速度 | ||
| 客戶要求安全合規 | 例如 ISO 27001 要求生產 API 與內部工具實體隔離 | ||
| 團隊分工需求 | 不同人負責不同系統,需要獨立部署權限與 SSH Key | ||
| 服務可用性 SLA | API 需保證 99.9% 不中斷,不能因為前端部署而 reload |
| 對比維度 | SDK 背景自動 PINGREQ (Keep-Alive) | 韌體主動定時 PUBLISH JSON 心跳 |
|---|---|---|
| 封包大小 | 極小 (僅 2 位元組) 標頭 0xC0 0x00,無 Payload |
較大 (約 150~500 位元組) 包含 TCP 標頭、MQTT 標頭與 JSON 內容 |
| 韌體程式碼工作量 | 零 (完全免寫代碼) 由 SDK 在背景執行緒/任務中全自動維護 |
中等 需維護硬體定時器、JSON 序列化與 Publish 狀態重試 |
| 硬體晶片功耗 | 極低 天線僅需發送 2 Byte,耗時極短,隨即進入深休眠 |
較高 天線發射資料時間長,需消耗更多 CPU 算力進行編譯 |
| AWS 與資料庫計費 | 零訊息費 / 極低 DB 費用 AWS 規定 Ping 包不收費。配合生命週期事件,僅於斷連線時寫入 DB |
正常收費 每次發送計入 AWS 訊息費,且每 60 秒強行觸發 Lambda 與寫入 DB |
| 適用場景 | 「在線狀態 (Online/Offline)」維護 不需回報具體硬體數據的純心跳判定 |
「設備狀態監控 (Telemetry)」 需要定時上報電量、訊號強度、溫濕度等真實數據 |
💡 物聯網混和架構最佳實踐 (Hybrid Strategy)
- 連線狀態:完全交給 MQTT SDK 底層 Keep-Alive (2-Byte PINGREQ) 處理,雲端配合 AWS IoT Lifecycle Events,達成零程式碼在線維護與極致省電。
- 硬體數據 (電量/訊號強弱):不要每 60 秒定時發送 JSON。改採「事件驅動上報」(如電量每降 5% 才發送一筆,或 WiFi 斷開重連時發送一筆)或「超長週期定期上報」(如每 12 或 24 小時發送一筆作為底線備份)。
5. 設備端 MQTT SDK 的獲取與配置指引
若要實現上述 Keep-Alive 長連接心跳,硬體研發團隊可以根據晶片與開發平台,透過以下方式獲取並配置 MQTT SDK:
☁️ AWS 官方物聯網 SDK (AWS IoT Device SDK v2)
官方針對微控制器、嵌入式 Linux 與各種語言有深度優化,內建完整的 mTLS(雙向證書加密)、自動重連與 Keep-Alive 機制。
- C / Embedded C SDK (最推薦微控制器):GitHub 官方庫 (適用資源受限的單晶片門禁機)。
- Python SDK (閘道器適用):安裝指令
pip install awsiotsdk。 - Node.js / JS SDK:安裝指令
npm install aws-iot-device-sdk-v2。
🔌 晶片廠商原生 SDK 與開源標準 Client
AWS IoT Core 完全相容標準 MQTT 3.1.1 及 5.0,因此所有支援 TLS 安全加密傳輸的標準 MQTT SDK 皆可開箱即用。
- ESP32 (ESP-IDF 官方框架):自帶標準
esp_mqtt,只需在配置結構體esp_mqtt_client_config_t中指明keepalive = 60;即可啟動底層背景自動 Ping 運作。 - Arduino IDE 生態:可直接於庫管理器搜尋安裝 PubSubClient 或 arduino-mqtt,並使用
setKeepAlive(60)設定。 - 工業開源標準 (Eclipse Paho):相容 C, C++, Java, Go 等,下載自 Eclipse Paho 官網。
🌡️ MQTT/溫濕度 數據對接
深度探討第三方廠商獲取設備溫濕度資料的三大核心對接架構與最佳實踐
1. 溫濕度數據對接系統架構 (System Architecture)
當硬體端(門禁機/環境監控器)採集到溫濕度數據後,會透過安全 MQTT 通道發送至 AWS IoT Core。以下是將這些溫濕度數據安全、高效地分享給第三方(如 PMS、物業管理軟體)的完整架構路徑:
graph TD
%% Base Nodes
subgraph "🔒 內部安全網絡 (Private Internal Network)"
Dev[硬體設備 / 溫濕度傳感器] -- "1. MQTT Publish (溫濕度 JSON)" --> AWS[AWS IoT Core]
AWS -- "2. Rules Engine (直寫 / 無 Lambda)" --> DB[(Firebase / DynamoDB)]
DB -.-> API[Cell API Manager (Nest/Express)]
end
subgraph "🏢 第三方介接網絡 (Third-Party Integration Network)"
API -- "方案一: REST API (GET)" --> REST[第三方 PMS / 物業管理伺服器]
AWS -- "方案三: 專屬 IAM 受限訂閱" --> MQTT[第三方 IoT 監控中心]
%% Webhook Path
AWS --> Lambda[AWS Lambda / Webhook Worker]
Lambda -- "方案二: Webhook POST (主動推播)" --> REST
end
classDef aws fill:#FF9900,stroke:#232F3E,stroke-width:2px,color:white;
classDef device fill:#10B981,stroke:#047857,stroke-width:2px,color:white;
classDef firebase fill:#FFCA28,stroke:#F57C00,stroke-width:2px,color:black;
classDef app fill:#3B82F6,stroke:#1D4ED8,stroke-width:2px,color:white;
class Dev device;
class AWS,Lambda aws;
class DB firebase;
class REST,MQTT,API app;
2. 三大數據對接方案深度對比
針對不同的業務場景與即時性需求,我們為第三方提供了三種不同的數據對接路徑:
方案一:被動拉取式 (REST API GET)
運作方式: 第三方伺服器在需要顯示環境數據時(例如:房務點開房間詳情頁),主動發送 GET /api/device/{id}/telemetry 請求。您的 API Manager 從 Firebase 讀取最新狀態快照並返回。
- 安全級別: 極高(受 API Gateway 與 JWT Token 嚴格保護)
- 即時性: 中等(取決於第三方查詢頻率)
- 適用場景: 常規儀表板顯示、每日環控報表統計
方案二:主動推播式 (Webhook POST)
運作方式: 第三方在您的平台註冊一個 Callback URL。當溫濕度數據發生顯著變化時(例如:溫度增減超過 ±0.5°C),您的 Webhook Worker 主動發送 HTTP POST 請求將最新 JSON 數據推給廠商。
- 安全級別: 高(透過網址簽章 HMAC SHA256 驗證)
- 即時性: 極高(毫秒級變更推播)
- 適用場景: 機房高溫警報、異常濕度即時通知
方案三:直連訂閱式 (AWS MQTT Subscription)
運作方式: 您在 AWS IoT Core 內為第三方簽發受限的憑證與安全策略(Policy)。廠商伺服器透過 MQTT 長連接訂閱限定的 Topic 路徑(如 partner/{partner_id}/device/{device_id}/telemetry)即時監聽。
- 安全級別: 中等(需精準配置 AWS IoT Policy,否則易有越權風險)
- 即時性: 極致即時(Sub-second 級別物聯網直連)
- 適用場景: 專業物聯網中控大屏、高頻率數據串流分析
| 對接指標 | 方案一:REST API (GET) | 方案二:Webhook (POST) | 方案三:MQTT 訂閱 (SUB) |
|---|---|---|---|
| 即時性 (Latency) | 低(依賴輪詢,有時間差) | 極高(事件變更立即推送) | 極致(亞秒級網路串流) |
| 安全維護難度 | 極低(標準 JWT 驗證) | 中等(需維護 Webhook 驗簽機制) | 極高(需維護 AWS IAM 與證書策略) |
| 伺服器頻寬開銷 | 高(廠商頻繁輪詢時會造成壓力) | 極低(僅變更時發送一次) | 中等(需維持 TCP 長連接維持線路) |
| 廠商對接門檻 | 極低(會寫 HTTP GET 即可) | 低(廠商僅需提供一個接收端點) | 高(廠商需實作 MQTT 客戶端與密鑰管理) |
⚠️ 平台多租戶安全防線(Security Warning)
- 絕對禁止將 Firebase 讀寫權限直接開放給第三方: Firebase 為內部業務微服務與私有 App 的直連通道。直接洩露 Firebase 憑證將導致多租戶資料隔離完全破產,屬於重大安全性漏洞!
- API 統一閘道原則: 第三方獲取任何硬體數據(溫濕度、開門日誌、連線狀態),必須統一經過您的 API Manager,利用 JWT 與設備授權關係表(Device Ownership Map)進行嚴格的物理性權限隔離。
- 數據節流控制: 在開放溫濕度對接時,需在 API Manager 層級限制查詢頻率(例如同一廠商限制每分鐘最多 30 次請求),避免惡意或不當編寫的第三方腳本癱瘓資料庫。
Nginx 反向代理設定
EC2 上的 Nginx 設定檔範本與部署指令備忘錄。
Nginx 設定檔範本
📄 /etc/nginx/sites-available/multi-app
server {
listen 80;
server_name 3.27.15.192;
# ─── Cell API Manager (Node.js via PM2) ───
location /pms/ {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# ─── Vsitth 訪客管理系統 (靜態前端) ───
location /vsitth/ {
alias /var/www/vsitth/;
try_files $uri $uri/ /vsitth/index.html;
}
# ─── 未來: 採購分析系統 (Node.js via PM2) ───
# location /bom/ {
# proxy_pass http://127.0.0.1:8899/;
# }
# ─── 預設首頁 ───
location / {
return 301 /pms/;
}
}
🔧 部署指令備忘
🚀 Vsitth 部署腳本 (本機端執行)
testVsit/Vsitth/ 專案目錄中,執行 vite build 後,
使用 rsync 將 dist/ 資料夾同步至 EC2 的 /var/www/vsitth/。
#!/bin/bash
# deploy-vsitth.sh
echo "=========================================="
echo "🚀 部署 Vsitth 訪客管理系統至 AWS EC2"
echo "=========================================="
# 1. Build
echo "📦 正在執行 Vite Build..."
npm run build
# 2. Upload
echo "📤 正在同步至 EC2..."
rsync -avz --delete \
-e "ssh -i ~/AWS_Key/pms-api-key.pem" \
dist/ ubuntu@3.27.15.192:/var/www/vsitth/
# 3. Reload Nginx
echo "🔄 重新載入 Nginx..."
ssh -i ~/AWS_Key/pms-api-key.pem ubuntu@3.27.15.192 \
"sudo systemctl reload nginx"
echo "=========================================="
echo "✅ 部署完成!"
echo "🌐 https://3.27.15.192/vsitth/"
echo "=========================================="
⚠️ 重要注意事項
部署至 EC2 後,必須在 Firebase Console → Authentication → Settings → Authorized domains 中, 將
3.27.15.192 加入授權清單,否則 Firebase Auth 登入功能會被阻擋。
若購買了域名 (例如
vsitth.thinkpos.com),可改用子域名搭配
Let's Encrypt 免費 SSL 憑證,輕鬆升級為 HTTPS 安全連線。
💰 費用暴增原因分析
深入解析 AWS IoT Core 的計費方式與異常飆高的真兇
為何帳單會倍數成長?
🧮 費用試算 (以 20 台設備為例)
- 發送頻率: 每台設備每 5 秒發送 1 筆 → 每分鐘 12 筆 → 每天 17,280 筆。
- 總發布量: 20 台設備每天產生 345,600 筆,一個月約產生 1,036 萬筆 寫入。
- 訂閱乘數: AWS IoT 的計費為「發佈」與「訂閱」分別計費。若您的後端伺服器 (Firebase/Node) 訂閱了這些資料,就會產生同樣 1,036 萬筆的「傳出訊息」費用。如果有 2 個後端或網頁開著,量就翻倍。
- 結論: 光是基礎傳輸,每個月就會產生近 2000~3000 萬筆計費訊息,這完全符合帳單呈指數上升的軌跡。
🛠️ 架構處理與優化方式
如何停止無效的頻繁發送,改用官方最佳實踐
導入 AWS IoT 生命週期事件 (Lifecycle Events)
在物聯網架構中,如果單純為了「確認設備存活」而頻繁發送資料,是非常昂貴且沒效率的。我們應改用底層的 Keep-Alive 機制。
1. 底層的 Keep-Alive 機制 (免收訊息費)
ESP32 與 AWS IoT 連線時,會約定一個 Keep-Alive 時間(如 60 秒)。若這段時間沒傳資料,ESP32 會自動發送超小的 PINGREQ 封包。AWS 不會對這種 PING 收取訊息費!
2. AWS 自動發佈斷線事件
一旦 AWS 偵測到 ESP32 沒發 PING (超時) 或正常斷線,系統內部會自動發一筆事件到隱藏頻道:
連線: $aws/events/presence/connected/{clientId}
斷線: $aws/events/presence/disconnected/{clientId}
3. 設定 IoT Rule 寫入 Firebase
- 到 AWS IoT 建立一個 Rule,語法:
SELECT * FROM '$aws/events/presence/#' - 觸發動作設定為 AWS Lambda
- Lambda 內將事件寫入 Firebase (例如
status: 'offline') - 效益: 一天一台設備大概只會觸發幾次事件,完全省下每天上萬次的浪費!
📊 高效率低成本架構圖
視覺化呈現 AWS IoT 與 Firebase 的完美整合
1. 系統元件架構圖 (Architecture Flow)
graph TD
subgraph 邊緣設備
ESP32[ESP32 硬體設備\n- 維持底層 PING\n- 僅在數值變化時發送資料]
end
subgraph AWS 雲端服務
IoT_Core[AWS IoT Core\n- 負責管理 MQTT 連線\n- 監控 Keep-Alive 超時]
Topic_Presence[內部隱藏主題\n$aws/events/presence/#]
IoT_Rule[AWS IoT Rule\n監聽連線與斷線事件]
Lambda[AWS Lambda\n處理狀態更新程式]
ESP32 -- "建立 / 中斷連線" --> IoT_Core
ESP32 -. "底層 MQTT PINGREQ\n(免收訊息費)" .- IoT_Core
IoT_Core -- "自動產生連線/斷線事件" --> Topic_Presence
Topic_Presence -- "觸發規則" --> IoT_Rule
IoT_Rule -- "呼叫" --> Lambda
end
subgraph Firebase 雲端資料庫
DB[(Firebase Database\n儲存設備最新狀態\nstatus: online/offline)]
Lambda -- "API 寫入/更新連線狀態" --> DB
end
subgraph 使用者端
APP[手機 App / 網頁儀表板\n- 隨時開啟隨時查看狀態\n- 無須喚醒硬體]
DB -- "即時狀態同步 (Listener)" --> APP
end
classDef aws fill:#FF9900,stroke:#232F3E,stroke-width:2px,color:white;
classDef device fill:#10B981,stroke:#047857,stroke-width:2px,color:white;
classDef firebase fill:#FFCA28,stroke:#F57C00,stroke-width:2px,color:black;
classDef app fill:#3B82F6,stroke:#1D4ED8,stroke-width:2px,color:white;
class ESP32 device;
class IoT_Core,Topic_Presence,IoT_Rule,Lambda aws;
class DB firebase;
class APP app;
2. 事件觸發時序圖 (Sequence Diagram)
sequenceDiagram
autonumber
participant ESP32 as ESP32 設備
participant AWS_IoT as AWS IoT Core
participant AWS_Rule as IoT 規則 (Rule)
participant Lambda as AWS Lambda
participant Firebase as Firebase DB
participant App as 手機 App
Note over ESP32, AWS_IoT: 平常待機狀態 (省錢模式)
loop 每 60 秒 (Keep-Alive)
ESP32->>AWS_IoT: 發送 MQTT PINGREQ (維持連線)
AWS_IoT-->>ESP32: 回傳 MQTT PINGRESP
end
Note over ESP32, App: 情境 A:設備異常斷線
ESP32-xAWS_IoT: 網路斷線 / 斷電 (無 PING 訊號)
AWS_IoT->>AWS_IoT: 偵測到 Keep-Alive 超時!
AWS_IoT->>AWS_Rule: 發佈至 $aws/events/presence/disconnected/
AWS_Rule->>Lambda: 攔截事件,觸發 Lambda
Lambda->>Firebase: 更新資料庫: status = "offline"
Firebase-->>App: 手機 App 即時推播 "設備已離線"
Note over ESP32, App: 情境 B:設備重新上線
ESP32->>AWS_IoT: 網路恢復,重新建立 MQTT 連線
AWS_IoT->>AWS_Rule: 發佈至 $aws/events/presence/connected/
AWS_Rule->>Lambda: 攔截事件,觸發 Lambda
Lambda->>Firebase: 更新資料庫: status = "online"
Firebase-->>App: 手機 App 即時推播 "設備已上線"
💡 建議實作做法與策略
給硬體與軟體團隊的最佳實踐指南
硬體端的降載策略
1. 延長傳輸週期
一般環境數據(溫濕度、空氣品質)不會在 5 秒內有劇烈變化。建議將一般待機時的資料傳送頻率從 5 秒改為 3~5 分鐘,如此可瞬間減少 95% 以上的通訊量。
2. 變化時才傳送 (Report on Exception)
在 ESP32 韌體加入邏輯:只有當感測器數值變化超過特定門檻(例如溫度差 0.5度、有人員進入),或者超過 5 分鐘沒發送時,才發佈訊息。兼具即時性與經濟性。
3. 打包資料 (Batching)
如果系統真的需要高頻率的數據點,可讓設備在內部記憶體收集 1 分鐘的資料後,打包成一個 JSON Array 一次發送。只要單筆 Payload 不超過 5KB,AWS IoT 均以 1 筆計費。
4. 手機 App 喚醒「即時模式」
- 當使用者打開手機 App 時,App 寫入一個 Flag (例如
mode: 'realtime') 到 Firebase。 - ESP32 訂閱該狀態,並將傳輸頻率暫時提高到 2 秒 1 次。
- 使用者關閉 App 後,將狀態復原,設備也切回 5 分鐘 1 次的省錢模式。