SYSTEM HANDBOOK
系統架構與維運說明
本頁說明網站使用的技術、資料如何查詢與排序、LINE 如何通知、排程如何設定,以及從官方 App 找出後端 API 的過程。頁面不包含任何帳密、Token 或 LINE User ID。
技術棧
| 層級 | 技術 | 用途 |
|---|---|---|
| 前端 | HTML5、CSS3、原生 JavaScript | 登入表單、響應式卡片/清單、LINE 訊息預覽;沒有前端框架與追蹤碼。 |
| 後端 | Node.js 20、Express 4、Axios | 接收查詢、呼叫館方 API、正規化資料及串接 LINE。 |
| 安全 | Helmet、express-rate-limit、HMAC-SHA256 | CSP/安全標頭、流量限制、LINE Webhook 驗簽及 Cron 保護。 |
| 通知 | LINE Messaging API | 以 Push/Multicast Message 發送到期提醒。 |
| 雲端 | Cloud Run、Cloud Build、Secret Manager、Cloud Scheduler | 容器部署、機密注入與每日定時觸發。 |
| 測試 | Node.js Test Runner | 驗證日期、API 解析、排序、訊息與排程篩選。 |
整個流程如何串接
1. 使用者送出帳密
2. Cloud Run 接收
3. 呼叫館方 API
4. 正規化及排序
5. 網頁呈現/LINE 推播
網頁即時查詢
- 表單以 HTTPS 將帳號及密碼送到
POST /api/query,按 Enter 與按查詢按鈕效果相同。 - 後端依官方 App 的規則將密碼做 UTF-8 Base64 後反轉,呼叫
patronAuth取得館內讀者識別碼。 - 再以該識別碼呼叫
patronLoans,取得書名、借閱日、到期日、續借次數與預約人數。 - 後端計算剩餘天數、優先原因並排序,僅回傳借閱資料,不回傳帳密或完整借閱證號。
- 瀏覽器產生卡片與 LINE 預覽;密碼不寫入 localStorage、Cookie 或應用程式日誌。
每日自動通知
- Cloud Scheduler 以 HTTPS POST 呼叫
/api/cron/run,並在標頭附上 Secret Manager 管理的 Cron 密鑰。 - 後端讀取 Secret Manager 注入的多帳戶設定,逐一向館方查詢。
- 依網頁排序保留第 1 至第 5 類書籍;沒有符合項目就不發訊息。
- 有符合項目時,以帳戶 label 組成訊息並透過 LINE Messaging API 推播。
原本 App 後端 API 如何取得
API 不是猜測網址,也不是爬 HTML;它是從「臺南市立圖書館 wow 愛讀冊」iOS App 的實際網路流量確認。使用的是方法 B:Windows 電腦執行 mitmproxy/mitmweb,iPhone 與電腦連同一個 Wi-Fi,再把 iPhone Wi-Fi HTTP Proxy 指向電腦的 8080 port。
- 電腦啟動
mitmweb,iPhone Safari 開啟http://mitm.it安裝 CA 描述檔。 - 在 iOS「設定 → 一般 → 關於本機 → 憑證信任設定」啟用完整信任。
- 確認 Safari 可經代理正常開網頁,再重新開啟 wow 愛讀冊 App。
- 在 App 登入並開啟借閱清單,於 mitmweb 依 host 與 JSON 回應篩選請求。
- 比對登入與借閱清單 request/response,確認 API base URL、
identify=TN、patronAuth、patronLoans與密碼編碼規則。 - 以測試程式重播請求,確認欄位後實作於
src/library-scraper.js。抓包完成後移除手機 Proxy;CA 憑證也可停用或刪除。
限制:這是官方 App 使用但未公開文件化的 API。館方可能隨時更換 URL、欄位或驗證方式;本系統沒有繞過帳號權限,也只能查詢使用者提供之有效帳戶。
查詢、判斷與排序規則
資料判斷
daysLeft < 0:已逾期,顯示負數逾期天數並標記優先歸還。reservationCount > 0:有人預約,標記優先歸還。renewCount ≥ 3:已續借三次以上,標記優先歸還。daysLeft = 0:今天到期,標記優先歸還。1 ≤ daysLeft ≤ 5:即將到期,符合自動 LINE 通知。
排序優先級
- 已逾期。
- 有人預約。
- 已續借至少三次。
- 今天到期。
- 一至五天內到期。
- 其他借閱書籍。
- 同一優先級內依到期日由早到晚;同時符合多項時採最高順位。
LINE 通知規則
- 每天排程逐帳戶查詢,依網頁六層規格排序,只通知第 1 至第 5 類;第 6 類其他書籍不通知。
- 若該帳戶沒有符合項目,完全略過,不傳送空訊息。
- 訊息顯示帳戶 label、查詢日期、書名、到期日與剩餘天數、續借次數、預約人數及優先歸還原因。
- 通知中的網站連結不含帳密,開啟後必須重新登入。
- LINE Channel Access Token、Channel Secret、通知 User ID 均由 Secret Manager 注入,不存在 Git。
- 手動推播只接受已登記在排程 Secret 的帳戶,並限制每個來源 IP 每小時最多三次。
排程時間與修改方式
正式環境時區固定為 Asia/Taipei(UTC+8),目前有兩個 Cloud Scheduler Job:
| Job | 時間 | Cron |
|---|---|---|
tainan-library-notify-morning | 每天 07:00 | 0 7 * * * |
tainan-library-notify-night | 每天 18:00 | 0 18 * * * |
查看設定
gcloud scheduler jobs describe JOB_NAME --location asia-east1 --project gen-lang-client-0942368175
修改時間
gcloud scheduler jobs update http JOB_NAME --location asia-east1 --schedule "0 8 * * *" --time-zone "Asia/Taipei" --project gen-lang-client-0942368175
上例會改成每天 08:00。修改排程不需要重新部署 Cloud Run;若更換 Cron Secret,則必須同步更新 Secret Manager、Cloud Run secret reference 與 Scheduler request header。
安全檢查與現況
| 項目 | 處理 |
|---|---|
| 完整借閱證號出現在 API response | 已移除。 |
| 公開手動 LINE 推播可遭濫用 | 只允許排程 Secret 內已登記帳戶,另設低頻率限制;通知對象不可由 request 指定。 |
| Inline JavaScript/CSS 導致 CSP 過寬 | 已拆成獨立靜態檔,禁止 inline script、object 與外部字型。 |
| 輸入及 request body 無明確上限 | 已限制欄位長度與 JSON 16 KB。 |
| Cron Secret 一般字串比較 | 已改為固定時間比較。 |
| 敏感 API 被代理快取 | 所有 /api response 加上 Cache-Control: no-store。 |
| 套件漏洞 | 部署前執行 production dependency audit 與自動測試。 |
仍存在的外部限制
- 館方 API 規格未公開,且其既有協定把編碼後密碼放在 HTTPS query parameter;傳輸受 TLS 保護,但上游若記錄完整 URL,理論上可能留下該值。此行為只能由館方更改 API 解決。
- 本服務不是館方官方服務;不可用於未經授權帳戶,也不應提高查詢頻率。
- 若需更高安全層級,可再加 Cloud Armor、服務端 session、Passkey 或僅限登入使用者存取。
維運與可增強功能
部署
npm test npm audit --omit=dev gcloud run deploy tainan-library --source . --region asia-east1 --project gen-lang-client-0942368175
建議後續順序
- 加入 Cloud Monitoring 告警,監測查詢失敗率與排程失敗。
- 若館方提供續借 API,再新增需二次確認的一鍵續借;目前不應以猜測端點實作。
- 加入受保護的帳戶管理介面,取代人工更新
SUBSCRIBERS_JSONSecret。 - 加入通知歷史與去重機制,避免同一天重複通知完全相同內容。