[實作筆記] 更新自架在 GCP VM 上的 n8n(Docker 版)

前情提要

自架 n8n 跑在 GCP VM 的 Docker container,
某天備份 log 出現這行:

1
Error tracking disabled because this release is older than 6 weeks.

意思是 n8n 的 release 超過六週就會關掉錯誤追蹤,提醒你該更新了。
趁機整理一下 Docker 版 n8n 的更新流程。


更新前先確認現況

確認目前版本

1
2
docker exec n8n n8n --version
# 2.16.1

確認 container 設定

更新要重建 container,所以要先抓出原本的設定:

1
2
3
4
5
6
7
8
9
docker inspect n8n --format '{{json .HostConfig.PortBindings}}'
# {"5678/tcp":[{"HostIp":"","HostPort":"5678"}]}

docker inspect n8n --format '{{range .Mounts}}{{.Source}}:{{.Destination}}{{end}}'
# /home/user/.n8n:/home/node/.n8n

docker inspect n8n --format '{{range .Config.Env}}{{println .}}{{end}}'
# N8N_SECURE_COOKIE=false
# ...(其他是 image 預設的,不用帶)

重點是找出三個東西:portbind mount 路徑自己設的 env


更新三步驟

Step 1:pull 最新 image

1
docker pull docker.n8n.io/n8nio/n8n

Step 2:停掉舊 container

1
docker stop n8n && docker rm n8n

這時候 n8n 停機,資料不會消失,因為資料在 VM 本地的 bind mount 目錄,
跟 container 的生命週期完全無關。

Step 3:用相同設定重新啟動

1
2
3
4
5
6
7
docker run -d \
--name n8n \
--restart unless-stopped \
-p 5678:5678 \
-v /home/user/.n8n:/home/node/.n8n \
-e N8N_SECURE_COOKIE=false \
docker.n8n.io/n8nio/n8n

-v 的路徑換成你自己的 bind mount 路徑,env 只帶你自己加的就好。


驗證

1
2
docker exec n8n n8n --version
docker ps | grep n8n

確認版本號,確認 container 狀態是 Up,完成。


補充:備份

更新前要不要備份?

如果你有設 bind mount,資料就在 VM 本地,container 停掉不影響。
但如果更新後 n8n 有 migration 問題,舊 DB 可能回不去。

保險做法是更新前先手動跑一次備份:

1
2
3
4
5
6
7
# 匯出所有 workflow
docker exec n8n n8n export:workflow --all --pretty \
--output=/home/node/.n8n/exports/workflows/

# 匯出所有 credentials
docker exec n8n n8n export:credentials --all --pretty \
--output=/home/node/.n8n/exports/credentials/

或者搭一個 cron job 每天自動備份、git push,就不用每次更新前手動跑:

1
2
# crontab -e
0 3 * * * /home/user/n8n-backup/backup.sh >> /var/log/n8n-backup.log 2>&1

小結

Docker 版 n8n 更新流程:

  1. docker pull — 拉新 image
  2. docker stop n8n && docker rm n8n — 移除舊 container
  3. docker run — 用相同設定重建

停機時間大約 30 秒。
關鍵是 bind mount:資料住在 VM,不住在 container,container 砍掉重練資料不受影響。

(fin)

[工具筆記] Prime Token vs MPG:兩種台灣電商金流模式的本質差異

前情提要

最近在電商專案要把虛構金流換成真實串接,比較了 TapPay、綠界、藍新三家。

比著發現原來存在著兩種金流串接的模式。

  • Prime Token 模式:TapPay、Stripe、Braintree
  • MPG(Merchant Payment Gateway) 模式:綠界 ECPay、藍新 NewebPay

兩種模式選錯,後面整個訂單系統的設計都會錯。


本質差異就一個問題:卡號從哪走?

兩種模式的根本問題只有一個:敏感卡號(PAN + CVV)的傳遞路徑

MPG 型金流(綠界、藍新跳轉)對消費者反而更透明——畫面明確跳到金流商的頁面,消費者看得出來自己在哪。

但是在使用者體驗上,會多一個斷點,以電商來說會有轉換率的問題

Prime 型(TapPay iframe)的取捨是體驗好但透明度低,信任靠的是商家品牌,不是技術可見性。

而這是一個取捨。


實作細節

Prime Token 的卡號欄位內嵌結帳頁(實際是 iframe,能改邊框字體但限制多)。

整個結帳流程在你自己的頁面,設計語言、外觀風格一致。

MPG 直接跳到金流方頁面,你的品牌設計到那一刻全部斷掉

但好處也很實際:同一個金流頁面能直接接信用卡 + ATM + 超商代碼 + LINE Pay。

兩者的開發量比較

Prime Token 你寫的東西:

  • 前端 SDK 載入 + mount 欄位
  • 後端一支 charge API
  • 一個結果頁

MPG 你多寫的東西:

  • 表單產生器(含簽章演算法)
  • 自動 submit 的轉址頁
  • NotifyURL endpoint(驗章 + 冪等 + 更新訂單,這支是核心)
  • ReturnURL endpoint(使用者回站只看結果,不可以當付款依據)
  • 訂單狀態機要多 awaiting_payment
  • 對帳邏輯(callback 沒到怎辦?要定時 query 嗎?)

MPG 看似簡單,實際工程量比 Prime Token 大,主要是 callback 的 edge case 多

補充: PCI DSS 是什麼 ?

Payment Card Industry Data Security Standard,簡稱 PCI DSS。

信用卡組織(Visa、Mastercard 等)聯合制定的資安標準,規定任何「碰到卡號」的系統都要符合一堆安全要求。

核心概念:碰到卡號的範圍越小,你要過的關越少。

實務上只有金流商或大型電商會去取得這種認証,一般而言小型電商還是透過第三方金流來實現交易。


小結

  • 兩種模式的本質差異是卡號的傳遞路徑:Prime Token 走 SDK iframe,MPG 走表單轉址
  • 信任是最難的

(fin)

[實作筆記] LINE Messaging API 用量查詢:免費額度怎麼看,以及為什麼通知會停止

前情提要

用 LINE Bot 做系統通知,結果某天發現通知完全停了,重啟也沒用。
查了一圈才發現根本原因不是程式問題,這篇記錄怎麼查用量、以及為什麼停的。


問題現象

LINE Bot 的 push 通知在某個時間點之後完全靜默。
程式沒報錯,因為程式碼裡的 push 用了 .catch(() => {}) 吃掉所有錯誤:

1
2
3
async function push(text: string) {
await lineClient.pushMessage(ALLOWED_USER_ID, msg).catch(() => {})
}

靜默失敗,完全看不出來哪裡出問題。


怎麼查用量

LINE 的用量統計不在 Developers Console,要去 LINE Official Account Manager

  1. 打開 manager.line.biz
  2. 選你的 Official Account
  3. 左側 → 分析訊息

可以看到:

欄位 說明
合計(所有訊息) Reply + Push 總和
Push 主動推播的則數
Reply 回覆訊息的則數

可以選日期範圍,最長 60 天。


免費額度是多少

LINE Messaging API 免費方案每月有 500 則免費訊息(Push + Reply 合計)。

超過之後每則需要付費,或者訊息會被擋下來(依帳號設定而定)。

500 則聽起來很多,但如果 Bot 跑 AI 對話,Claude 回應很長,常常一次就切成好幾則 push(每則上限 5000 字),很快就會耗光。


這次的真正原因

查完用量才發現:5/1 用了 141 則,5/2 用了 59 則,5/3 之後全部是 0。

5/3 之後不是「發了但被擋」,是根本沒有觸發 webhook。

原因是 LINE Bot 用 Webhook 模式,需要一個對外的 HTTPS URL。
我的架構是:

1
LINE 伺服器 → HTTPS Webhook URL → Cloudflare Tunnel → 本機 Express server

Cloudflare Tunnel 的 launchd service 在 5/3 掛掉,LINE 打不到 webhook,所以什麼都沒發生。


Telegram 為什麼更穩

Telegram 用 Long Polling 模式,bot 主動去 Telegram 伺服器拉訊息,不需要對外開放任何 URL。

LINE Telegram
連線模式 Webhook(需要 tunnel) Long Polling(不需要)
斷線風險 tunnel 掛、IP 變、PORT 未開 幾乎沒有
免費額度 500 則/月 無限制

如果是用來做系統通知(不可中斷),Telegram 比 LINE 穩定很多。


小結

  • LINE 用量在 LINE Official Account Manager → 分析 → 訊息
  • 免費方案 500 則/月,用完靜默失敗
  • Webhook 模式依賴 tunnel,tunnel 掛掉通知就停
  • 對穩定性要求高的通知場景,Telegram Long Polling 更適合

(fin)

[AI生成]20260505 周報

本周要點

其他訊息

(fin)

[AI生成]20260504 周報

本周要點

其他訊息

(fin)

[AI生成]20260502 周報

本周要點

其他訊息

(fin)

[實作筆記] 個人自動化平台(七) n8n 備份自動化:workflow + credentials → GitHub

前情提要

n8n 跑起來、workflow 也在跑了,下一個問題:掛掉怎麼辦?

VM 重開機沒問題,但容器砍掉重建、或換 VM,workflow 和 credentials 就都要重設。
這篇記錄怎麼用 CLI export + git 做自動備份。


備份方案決策

考慮過幾個方向:

方案 問題
備份整個 .n8n 目錄 SQLite 每次執行都寫入,幾乎天天變動,太肥
只備份 workflow JSON 沒有 credentials 無法完整還原
n8n Source Control(GitOps) Enterprise/Business 付費功能,Community 版不支援
CLI export workflow + credentials 可行,輕量,Community 版都有

最終選 CLI export + GitHub private repo,搭配 cron job 每天自動跑。


架構

1
2
3
4
GCP VM(cron job 每天凌晨 3 點)
└─ docker exec n8n → export workflow + credentials JSON
└─ bind mount(/home/user/.n8n)
└─ 複製到 repo backup/YYYYMMDD-NNN/ → git commit + push → GitHub

每次跑都建立新的日期資料夾(20260430-00120260430-002),保留最近 30 份,舊的自動刪除。就算刪了,git log 還是查得到歷史。


實作

Repo 結構

1
2
3
4
5
6
7
8
Marsen.Butler.n8n.Backup/
├── README.md
├── install.sh ← 一次性安裝,設定 cron job
├── backup.sh ← 每次執行的備份邏輯
└── backup/
└── 20260430-001/
├── workflows/
└── credentials/

backup.sh 核心邏輯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# container name 預設 n8n,可用環境變數覆蓋
N8N_CONTAINER="${N8N_CONTAINER:-n8n}"
KEEP_COUNT="${KEEP_COUNT:-30}"

# 產生日期流水號資料夾
DATE_TAG="$(date +%Y%m%d)"
SERIAL=$(printf "%03d" $(( $(ls -d backup/${DATE_TAG}-* 2>/dev/null | wc -l) + 1 )))
BACKUP_DIR="backup/${DATE_TAG}-${SERIAL}"

# 確認容器在跑,export,複製到 repo...

# 刪除舊備份(保留最近 KEEP_COUNT 份)
TOTAL=$(ls -d backup/[0-9]* 2>/dev/null | wc -l)
if [ "$TOTAL" -gt "$KEEP_COUNT" ]; then
ls -d backup/[0-9]* | sort | head -n $(( TOTAL - KEEP_COUNT )) | xargs rm -rf
fi

# 每次都 commit
git add backup/
git commit -m "backup ${DATE_TAG}-${SERIAL}"
git push

docker inspect 自動找 bind mount 路徑,不寫死,換 VM 也通用。

install.sh

1
2
3
4
5
6
7
8
9
# 設定每天凌晨 3 點執行
CRON_JOB="0 3 * * * ${BACKUP_SCRIPT} >> /var/log/n8n-backup.log 2>&1"

# 避免重複寫入
if crontab -l 2>/dev/null | grep -q "$BACKUP_SCRIPT"; then
echo "Cron job already exists, skipping."
else
(crontab -l 2>/dev/null; echo "$CRON_JOB") | crontab -
fi

踩坑記錄

git clone 下來的腳本沒有執行權限

git pull 之後直接跑 ./backup.sh,結果:

1
-bash: ./backup.sh: Permission denied

原因:git 預設不保留 chmod +x,clone 下來的檔案是 644。

解法:用 git update-index 把執行權限記進 git:

1
2
3
git update-index --chmod=+x backup.sh install.sh
git commit -m "fix: preserve execute permission for scripts in git"
git push

之後 git pull 拿到的檔案就自帶執行權限,不需要手動 chmod

Container name 不應該硬綁定

一開始寫死 N8N_CONTAINER="n8n",但我的容器叫 n8n-01,直接報錯。

改為環境變數覆蓋,預設值 n8n

1
N8N_CONTAINER="${N8N_CONTAINER:-n8n}"

臨時要換:N8N_CONTAINER=n8n-01 ./backup.sh,不需要改腳本。

備份會把 token 明文存進 git

備份的是 workflow JSON,而 workflow JSON 裡如果 token 是直接寫在節點裡,那備份就等於把 token 存進 git repo。

用文字搜尋就找得到:

1
grep -r "access_token\|IGAA" backup/

解法:改用 n8n Credential 存 token

n8n 的 Credential 有獨立加密機制,workflow JSON 裡只會存 Credential 的 ID,不含實際值。這樣備份出來的 workflow 就不會洩漏 token。

1
2
3
4
5
# 不好(硬寫在節點)
"access_token": "IGAAYLng3..."

# 好(引用 Credential)
"credentials": { "instagramOAuth2Api": { "id": "xxx", "name": "IG" } }

Credential 本身也可以 export 備份,但它是加密的(用 N8N_ENCRYPTION_KEY),只要 Key 沒洩漏,備份裡的 Credential 就是安全的。


VM 上安裝步驟

1
2
3
4
5
6
7
8
9
# 1. clone repo
git clone [email protected]:marsen/Marsen.Butler.n8n.Backup.git ~/n8n-backup

# 2. 安裝 cron job
cd ~/n8n-backup
./install.sh

# 3. 手動跑一次確認
./backup.sh

還原

情境一:同 VM,換新容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker run -d \
--name n8n \
-e N8N_SECURE_COOKIE=false \
-v /home/user/.n8n:/home/node/.n8n \
-p 5678:5678 \
--restart unless-stopped \
docker.n8n.io/n8nio/n8n

# 取最新備份
LATEST=$(ls -d ~/n8n-backup/backup/[0-9]* | sort | tail -1)
cp -r "${LATEST}/workflows/." /home/user/.n8n/exports/workflows/
cp -r "${LATEST}/credentials/." /home/user/.n8n/exports/credentials/
docker exec n8n n8n import:workflow --separate --input=/home/node/.n8n/exports/workflows/
docker exec n8n n8n import:credentials --separate --input=/home/node/.n8n/exports/credentials/

情境二:新 VM

從密碼管理器取出 N8N_ENCRYPTION_KEY,啟動時帶入:

1
2
3
4
5
6
7
8
docker run -d \
--name n8n \
-e N8N_ENCRYPTION_KEY=你的key \
-e N8N_SECURE_COOKIE=false \
-v /home/user/.n8n:/home/node/.n8n \
-p 5678:5678 \
--restart unless-stopped \
docker.n8n.io/n8nio/n8n

clone repo 並還原最新備份:

1
2
3
4
5
6
git clone [email protected]:marsen/Marsen.Butler.n8n.Backup.git ~/n8n-backup
LATEST=$(ls -d ~/n8n-backup/backup/[0-9]* | sort | tail -1)
cp -r "${LATEST}/workflows/." /home/user/.n8n/exports/workflows/
cp -r "${LATEST}/credentials/." /home/user/.n8n/exports/credentials/
docker exec n8n n8n import:workflow --separate --input=/home/node/.n8n/exports/workflows/
docker exec n8n n8n import:credentials --separate --input=/home/node/.n8n/exports/credentials/

Credentials 是用 N8N_ENCRYPTION_KEY 加密儲存,還原時必須用相同的 Key,否則全部無法解密。Key 存密碼管理器,不存 repo。


參考

小結

  • CLI export 是 Community 版可用的最輕量備份方案
  • 每次建日期資料夾(YYYYMMDD-NNN),保留最近 30 份,超過自動 prune
  • 就算資料夾被刪,git log 還是查得到歷史
  • git update-index --chmod=+x 讓腳本執行權限跟著 repo 走
  • N8N_CONTAINER 環境變數讓腳本不寫死 container name
  • 還原關鍵:N8N_ENCRYPTION_KEY 要另外存好

(fin)

[實作筆記] 技術面試 Q&A:大流量電商系統設計實戰

前情提要

整理一次技術面試的對話紀錄,主題圍繞在大流量電商系統的設計與應變。

面試公司:H 社,北台灣,科技服務業,員工數十人。實際業務為線上遊戲平台的開發與維運;產品涵蓋自研遊戲、第三方遊戲接入,以及面向代理商與玩家的平台系統。整體採微服務架構,開發部門數十人,小組制運作。跨國營運,技術端設於台灣。

職位:後端為主的全端工程師,技術棧含 Node.js / Go、現代前端框架、SQL / NoSQL,要求具備架構設計與 CI/CD 實務能力,並承擔技術指導職責。

以下按主題整理成 Q&A,並附上現代技術視角供參考。


大流量與快取架構

Q:電商搶購時流量瞬間暴增,怎麼擋?

先接受「讓使用者排隊,不讓網站 crash」這個前提,整個設計才會清楚。

具體做法是多層 Cache 疊加:

  • 前端:搶購期間關掉非核心的資訊(推薦、廣告等),減少後端壓力
  • 後端第一層:庫存資料放 Redis
  • 後端第二層:使用 MemoryCache(.NET 4.5/4.6 內建)做本地快取
  • DB 層:DB 自身的查詢快取

流量依序被每一層擋掉,真正打到 DB 的請求量才能控制住。

現代解法:Kubernetes + HPA(Horizontal Pod Autoscaler)根據 CPU / Memory 自動擴縮容,不再需要預先備機和人工值班。Redis 依然是標準選擇,但搭配 Lua Script 或 WATCH/MULTI/EXEC 可更精確控制庫存的原子操作,避免超賣。


Q:那時候沒有 Kubernetes,怎麼做水平擴充?

用 VM 搭配手動的負載分流。備用機平時待機,搶購前預先準備好;緊急時把流量導過去。

沒有自動 scale,所以需要人工值班監控——雙 11 期間三班制,值班加 on-call 全員待命。

現代解法:Kubernetes 的 Deployment + HPA 讓水平擴充自動化,節省人力成本。搭配 Cluster Autoscaler,甚至連底層節點都能自動增減,不再需要預先開好備用機等待。


監控與事故應對

Q:系統是 crash 了才知道,還是有提前預警?

有兩個觸發條件:

  1. 資源閾值警報:CPU 或 Memory 超過 80% 就通知,不等到崩潰
  2. 錯誤日誌頻率:同類型錯誤在短時間內高頻出現,自動告警

這些監控都是自己開發的,那個年代沒有現成的 APM 工具可以直接套。

現代解法:Prometheus + Grafana 是現在的標準配置,幾乎不需要自己開發。資源閾值警報用 AlertManager 設定即可;錯誤頻率則可搭配 ELK Stack 或 Loki,對 log 做 rate 統計觸發告警。SaaS 選項如 Datadog、New Relic 更是開箱即用。


Q:真的發生過事故嗎?當下怎麼處理?

有發生過,包含主機掛掉切備用機、備用機跟著掛掉的連環情況。

第一線的原則是:先讓系統回來,問題找根因可以之後再說

當下操作是執行預先備好的備案(切流量、重啟服務),同時通報讓有能力解決根本問題的人起來處理。

事後改進:正式搶購活動前一定先壓測,模擬預估流量的 110%,只測核心購物流程,把能關的功能全部關掉。

現代解法:事故應對流程本身可以標準化為 Runbook,記錄每種已知情境的處置步驟,讓值班工程師不需要靠記憶應急。Kubernetes 搭配 liveness/readiness probe 可自動偵測服務不健康並重啟,減少人工介入的時間窗口。壓測工具如 k6 或 Locust 可整合進 CI/CD,讓每次上線前自動跑一輪基準測試。


金流系統設計

Q:對接多個金流廠商,程式碼怎麼不變成一團亂?

用 Strategy Pattern 的概念,把金流流程切成 Workflow。

整個下單流程是一條工作站鍊:

1
商品計算(折扣、贈品)→ 訂單建立 → 金流付款

金流工作站本身的邏輯很薄:根據使用者選的付款方式,決定走哪個 Strategy。不同廠商(信用卡、超商取貨、銀行轉帳)各自封裝成獨立模組,主流程不在意裡面怎麼串接。

新增金流廠商:加一個新模組,主流程不用改。

現代解法:這個架構今天依然適用,核心概念沒有過時。如果系統更大,可以把每個金流廠商拆成獨立的微服務,透過統一的 Payment Gateway API 對外暴露,主流程完全不感知廠商差異。設計模式上除了 Strategy,也可以考慮 Plugin Architecture,讓新廠商直接以套件形式掛入,不需要改動核心程式碼。


Q:訂單成立了,金流卻失敗,怎麼處理?

訂單成立和金流付款是兩個獨立的 Transaction。

金流失敗不會 rollback 訂單,而是讓訂單保持「待付款」狀態,通知使用者重新付款或換付款方式。

好處是:金流失敗是常見的可預期錯誤,這樣設計使用者體驗比較好,也避免訂單資料不一致。

現代解法:在微服務架構下,這個問題升級為分散式交易問題。現代標準解法是 Saga Pattern:每個步驟成功後發事件觸發下一步,失敗時執行 compensating transaction 回滾前面的狀態,整個流程不依賴跨服務的 ACID Transaction。Choreography Saga 適合步驟簡單的流程,Orchestration Saga 適合需要集中管控的複雜流程。


AI 與本地部署

Q:你做的法律 AI 系統,為什麼不用雲端大模型就好?

客戶是大型企業(Sony、政府機關、醫療機構),他們不願意把公司內部資料送上雲端。

做法是把 AI 系統打包進實體機器,送進客戶的內網部署。功能類似 ChatGPT,但跑在客戶自己的機房裡。

主要應用場景是:

  1. RAG:上傳法律文件,AI 從裡面查詢相關資訊
  2. 專利風險分析:原本需要半年人工查詢各大專利資料庫,自動化後大幅縮短

現代工具:本地部署 AI 的技術棧已經成熟許多。Ollama 可以一鍵跑主流開源模型(Llama 3、Mistral 等),LlamaIndexLangChain 提供 RAG pipeline 的標準建構塊,向量資料庫用 ChromaDBQdrantWeaviate 都可以,整個 stack 都可以打包進客戶內網。對於需要更強模型的場景,也可以考慮 vLLM 搭配企業級 GPU,在不上雲的前提下跑到生產等級的推論效能。


關於 AI 對開發的影響

Q:你覺得 AI 工具對軟體開發的影響是什麼?

一個工程師現在能產出的東西,可能相當於十年前一整個小團隊的產出。

但這也帶來新的問題:程式碼產出速度快了,但架構決策、程式碼品質的門檻反而更重要。AI 可以快速生出大量程式碼,但如果架構設計不對,技術債也會累積得更快。

進一步的觀察:Coding style 和命名規範這類問題,現在完全可以交給 AI 處理——把 guideline 寫好,讓 AI 執行和稽核就好,不值得花人力在上面爭論。真正需要人把關的是 Domain 層和 Use Case 層的設計:商業邏輯有沒有被正確封裝?Unit Test 有沒有覆蓋核心行為?這兩層做對了,架構就不容易爛掉。六邊形架構(Hexagonal Architecture)或 Clean Architecture 在 AI 輔助開發的時代反而更值得落地,因為邊界清楚,AI 才不容易在錯誤的地方塞邏輯。


面試反思

用業界術語包裝已知的答案

描述分散式交易的處理方式時,邏輯是對的,但沒有說出 Saga Pattern 這個詞。面試官如果熟悉這個詞,會更快建立信心;說不出來反而讓人以為是靠直覺摸索,不是系統性的設計知識。同樣的,提到 Request ID 追蹤時,可以直接說 Distributed TracingOpenTelemetry,展示對現代標準的認識。術語不是裝飾,它是讓對方快速理解你程度的捷徑。

主動帶出現代解法

談電商 N 社那個年代的做法時,可以補一句「現在我會用 Kubernetes + HPA 處理這個問題」——展示持續學習的態度。技術知識有,但沒有主動說出來,廣度就沒有完整呈現。

這兩點對應的技術題目值得另外整理成文章研究:Saga Pattern、Distributed Tracing、OpenTelemetry。


小結

  • 大流量不靠單一技術解決,是多層 Cache + 系統開關 + 人工值班的組合
  • 監控要在系統崩潰前就觸發,資源閾值和錯誤頻率是兩個實用的指標
  • 事故處理分兩階段:先讓系統回來,再找根因
  • 金流複雜性用 Strategy Pattern + Workflow 拆解,廠商細節封裝進模組
  • 本地 AI 部署是企業安全性需求的實際解法,不只是技術選擇

(fin)

[實作筆記] 個人自動化平台(番外) Google OAuth redirect_uri_mismatch 除錯記錄

前情提要

個人自動化平台(六) 實作 IG Token 工具時,

為了修 Instagram OAuth callback URL 的問題,在 Vercel 設了 NEXT_PUBLIC_BASE_URL

沒想到這個動作讓 Google 登入炸掉,報 redirect_uri_mismatch

表面上是「連帶損傷」,但根本原因是 production 的 Google 登入從來沒測試過,問題早就存在,只是這次才被發現。


事件時間線

背景:production Google 登入從來沒運作過

NEXT_PUBLIC_BASE_URL 在 Vercel 上沒有設值時,env.baseUrlundefined,Google OAuth 送出的 redirect URI 是:

1
undefined/api/auth/google/callback

這個字串不可能通過 Google 驗證。

但 production 上從來沒有人測試過 Google 登入,所以一直沒被發現。

本機開發設的是 NEXT_PUBLIC_BASE_URL=http://localhost:3000,Google Console 也只登記了 http://localhost:3000/api/auth/google/callback,本機正常,所以沒有警覺。

起點:IG Token 工具的 Instagram OAuth callback URL 錯誤

實作 IG Token 工具時(demo.marsen.me),Instagram 授權完成後 callback 沒有導回正確的頁面,而是跑到 Vercel 內部 URL。

原因:Instagram callback route 的 baseUrl 在 Vercel 上沒有設定,fallback 讀到的是 Vercel 執行環境的 host,不是對外的 demo.marsen.me

修法:在 Vercel 設定 NEXT_PUBLIC_BASE_URL

在 Vercel 環境變數加上:

1
NEXT_PUBLIC_BASE_URL=https://demo.marsen.me

因為專案用 vercel build --prebuilt 方案,直接 Redeploy 不會重新 build,必須 push 新的空 commit 觸發 GitHub Actions:

1
2
git commit --allow-empty -m "chore: trigger redeploy"
git push origin main

Instagram OAuth callback 正常了。

問題浮現:第一次在 production 測試 Google 登入

設了 NEXT_PUBLIC_BASE_URL 後,第一次真正在 production 試 Google 登入。

NEXT_PUBLIC_BASE_URL 同時影響 Google OAuth 的 redirect URI(src/app/api/auth/google/route.ts):

1
2
3
4
5
const google = new Google(
env.googleClientId,
env.googleClientSecret,
`${env.baseUrl}/api/auth/google/callback`,
)

app 送出的 redirect_uri 變成:

1
https://demo.marsen.me/api/auth/google/callback

但 Google Cloud Console 只登記了本機的那條:

1
http://localhost:3000/api/auth/google/callback

不符,報 redirect_uri_mismatch


症狀

點「Google 登入」後,Google 跳出錯誤頁面:

1
2
已封鎖存取權:這個應用程式的要求無效
發生錯誤 400:redirect_uri_mismatch

解法

進 Google Cloud Console → Credentials → OAuth 2.0 Client → Authorized redirect URIs,新增:

1
https://demo.marsen.me/api/auth/google/callback

本機那條 http://localhost:3000/api/auth/google/callback 保留,本機開發還用得到。

存檔幾分鐘內生效,不需要重新 build 或 deploy。


參考

小結

  • 根本原因不是「設定改壞了」,而是 production Google 登入從來沒被測試過,問題一直存在
  • IG Token 工具需要在 Vercel 設 NEXT_PUBLIC_BASE_URL,這個動作讓潛藏的問題第一次浮現
  • redirect_uri_mismatch 代表 app 送出的 redirect URI 和 Google Console 登記的不符
  • NEXT_PUBLIC_BASE_URL 影響所有 OAuth 服務,改了就要同步更新各 Console 的 redirect URI
  • Google Console 可以同時登記多條 URI,本機和 production 各一條,互不影響
  • vercel build --prebuilt 的專案,env var 更新後要 push 空 commit 才會生效

(fin)

[實作筆記] 個人自動化平台(五) n8n 實作:處理層,Aggregate + Gemini 週報整理

前情提要

收集層跑通後,每次執行輸出統一格式的文章列表。

這篇記錄處理層:把多筆文章合併,送給 Gemini 整理成 AI 週報。

未來也可能抽換成不同的雲端或地端 AI 模型服務。


為什麼要 Aggregate?

收集層輸出的是多筆獨立資料(以 RSS 為例,一次可能拿到 10 筆)。

Basic LLM Chain 預設對每筆各送一次請求,10 筆 = 10 次 API call,超過 Gemini 免費版 5 RPM 限制。

Aggregate 節點先把多筆合成一筆,再送一次請求給 AI。這也更符合「週報」的本意——要的是一份完整報告,不是 10 篇獨立摘要。

1
Edit Fields → Aggregate(All Item Data)→ Basic LLM Chain(Google Gemini)

Prompt 設計

1
2
3
4
5
6
週報日期:{{ $now.toFormat('yyyy年MM月dd日') }}

以下是本週 AI 新聞,請用繁體中文整理成一份週報。
每則新聞說明「是什麼」和「為什麼重要」,條列呈現。

{{ $json.data.map(i => `【${i.source}】${i.title}\n${i.content}`).join('\n\n') }}

執行結果:日期正確,內容有條理,每篇清楚說明「是什麼」和「為什麼重要」。


踩坑:Rate Limit

第一次直接把 10 筆送給 LLM,遇到 429 Too Many Requests

1
quota exceeded: limit 5 requests/minute (gemini-2.5-flash free tier)

解法:加 Aggregate 節點合併成一筆,從 10 次請求降為 1 次,問題消失。


參考

小結

  • Aggregate 節點把多筆合成一筆,是處理層的關鍵,解決 rate limit 也符合週報語意
  • Prompt 用 expression 動態帶入日期與新聞內容,輸出穩定

(fin)

Please enable JavaScript to view the LikeCoin. :P