iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Vibe Coding

《我與 AI 的奇幻漂流:30 天,把「能跑」變成「能上線」》系列 第 17 篇

【Day 17|船舷之外】API 實作與請求驗證:送進來的資料,真的能信嗎?

  • 分享至 

  • xImage
  •  

昨天結尾,留了兩個問題:

  • 如果只在畫面上的輸入框上限制字數,真的擋得住嗎?
  • 頁面自動帶入的 id,能不能相信?

昨天我們其實就已經知道答案:畫面不是唯一的入口。 任何人都能跳過表單,直接送出請求。在海上,船舷之外漂來的東西,不能看都不看就拉上船。網站也一樣:從外面送進來的資料,要先檢查過,才能放進資料庫。

今天要做的,是專案中第一個讓使用者的操作能「寫入」資料庫的功能:回報資料錯誤。 使用者發現某張專輯的內容物不對,可以直接在詳情頁送出一則回報,將回報紀錄寫進資料庫後,我會接著收到通知。

不過,今天我想換個方式開始。


一、只描述需求,先問 AI

如果是以前,我大概會先自己思考一下:需要什麼資料表、需要什麼 API、送什麼欄位、回什麼狀態碼,然後才開始寫。但現在,我更常先做另一件事:告訴 AI 我想要什麼,請它把這個需求展開。

我想在專輯詳情頁加入「回報資料錯誤」功能。
使用者可以針對目前這張專輯/版本送出一段文字回報。
送出後我希望資料能被保存,而且我能收到通知。

先不要寫程式。請先分析:

  • 這個功能會影響哪些地方?
  • 前端、後端、資料庫分別需要做什麼?
  • 有哪些安全性與驗證問題?
  • 你建議的實作順序是什麼?
  • 預計會修改哪些地方?

分析完後,告知我結果,並且另外以 1~5 句話總結,還不用實作。

這段 prompt 裡提分析的問題,也是刻意的。如果只說一句「幫我做一個回報功能」,也做得出來,但它有可能會照最直接的方式做:一個表單、一張表,能送出就好。它不一定會主動去想,送進來的資料能不能信、通知失敗怎麼辦。

AI 想得多深,很大一部分取決於你問了什麼。 (或是模型本身的能力) 請它先回答「會影響哪些地方」「有哪些安全性與驗證問題」,就是在逼它把這些事想過一遍。先展開、再動手,寫出來的程式通常會相對穩定得多。

這次 Codex 有自己先讀專案的程式碼,再提出計畫。如果你用的工具不會,可以在 prompt 裡加一句:「請先閱讀目前跟專輯詳情、資料存取相關的程式碼,再提出計畫。」
通常建議還是要看一下他過程中有沒有真的讀程式碼,或是看一下他的結果跟程式碼內容是否符合。有時候即便在提示詞中請他看過程式碼,他會沒有看,但回應的時候還是說他有看過。


二、一個功能,會動到四層

AI 先讀了專案裡的程式碼,再給出它的分析。整理起來,一個「回報」功能,會動到四個地方:

專輯詳情頁的回報表單(前端)
        │  POST /api/data-reports
        ▼
專案自己的 API(後端):先檢查送來的內容
        │
        ├── ① 存進 data_reports 資料表(資料庫)
        │
        └── ② 存好之後,由後端通知 Telegram(外部服務)→ 我的手機
層 要做什麼
前端 在詳情頁加一個按鈕和表單,自動帶入目前的專輯和版本,顯示送出中、成功、失敗
後端 新增一支 API,重新檢查所有內容,存進資料庫,再發通知
資料庫 新增一張回報用的資料表,並且設定權限:訪客不能讀、也不能直接寫
外部服務 收到回報後,通知我

其中有一個細節讓我印象很深:AI 發現專輯的完整頁和側邊抽屜,共用同一個元件,所以回報表單只要加在一個地方,兩邊就都有了。這是它先讀過程式碼,才能給出的建議。

這件事通常也跟寫程式的風格有關,重複使用到的元件,我們並不會重複寫一次,而是將它抽成一個元件,直接共用,這樣相對來說有比較高的可維護性。


三、審:用昨天學到的東西對一遍

AI 的計畫很長。但不管多長,我通常會先確認過,例如可以先拿昨天的清單,逐條對照:

要問的問題 為什麼要問 AI 的計畫
資料從哪條路寫進去? 如果讓瀏覽器用公開金鑰直接寫入,任何人都能跳過畫面 ✔ 走自己的後端 API
檢查放在前端還是後端? 前端的限制,用 Postman 就能繞過 ✔ 前後端都檢查,明講「不能信任前端」
自動帶入的 id,後端有沒有再查? 昨天說過,id 要填什麼都可以 ✔ 後端會再查一次;資料庫也用「專輯 id 加版本 id」兩個欄位一起的外鍵,保證版本一定屬於這張專輯
金鑰和 token 放在哪裡? NEXT_PUBLIC_ 開頭的,會被送到瀏覽器 ✔ 全部只放伺服器,管理用的連線獨立成一個檔案
通知失敗時,回報會不會不見? 通知只是附加功能,不該拖累主要流程 ✔ 先存再通知,通知失敗也保留回報
失敗的時候,使用者會看到什麼? AI 很容易只規劃一切順利的情況 ✔ 送出中、成功、失敗都有對應的畫面

有時候,交由其他的模型來進行判斷,也是一個我蠻常用的方法。但兩個 AI 說法一樣,不代表就是一定是對的(但錯的機率會降低),最後還是要回到程式碼和實際測試。


四、收:哪些我決定,哪些接受預設

因為 prompt 中有請它把安全和驗證都想過一遍,這次 AI 展開得很完整,甚至有點多。

除了基本功能,計畫裡還有限流、防機器人、重複內容攔截、通知狀態、通知重試……每一項確實都有道理。展開之後,接下來就是由我把它收回來。

4.1 我做的決定

有幾件事,AI 提供了選項,要由我決定:

問題 AI 的建議 我的決定 理由
用什麼通知我? Email Telegram 低流量、想即時看到,Telegram 最直接。AI 也說,換通知管道只影響通知那一層
要不要收回報者的聯絡方式? 選填 Email 不收 用不到的個人資料,就不要收 (但如果想要讓使用者知道處理進度,其實也可以幫一個選填資料)
通知送到哪裡? 私人群組 私人聊天 有這方面實作經驗
字數限制 10~2000 字 暫定1 ~ 50 字 問題應該通常不會太長,後續再視情況增加

我通常也都會在提示詞中就告知,請他展開一些選項、做法的時候,先不要替我做決定,而是去分析優缺點,後續再由我決定。

其中,字數限制其實值得想一下。AI 原本建議 10~2000 字,我則暫定上限 50 字,認為一般的資料回報應該不需要太長。但反過來想,如果有人只想回報「小卡少一張」,這樣的內容連 10 個字都不到,難道就不算有效回報嗎?所以我後來決定最少要一個字。

AI 建議的限制,不只是確認它有沒有實作,也要想想會不會連正常使用者一起擋掉。 所以除了設定上限,下限要不要存在,也應該由實際使用情境來決定。

4.2 我接受的預設,和它的代價

其他的部分,我大多採用了 AI 的預設。但接受之前,我要先釐清每一件事情在做什麼:

AI 加上的東西 白話 在擋什麼
限流 同一個來源,每小時最多送 5 則 有人用程式一直送
隱藏欄位 表單裡藏一個人看不到的欄位,只有機器人會填 自動亂填表單的機器人
重複內容攔截 10 分鐘內送出一模一樣的內容,就擋下來 手滑連按,或同一段內容一直送
通知狀態 記錄每則回報的通知,是成功、失敗,還是還沒送 Telegram 暫時故障時,事後還查得到哪些沒送出去
整張專輯或目前版本 使用者可以選擇回報的範圍 有些錯誤是整張專輯的,不是某個版本

以這個專案現在的規模,這幾項不一定每一項都非做不可。我把它們留下來,一部分也是當作示範:等網站變大、開始被有心人士盯上,這些防護就派得上用場,而且不用到時候才從零開始補。
有了 AI,這些防護現在加起來很快。但前提是,你得先知道有這些東西,才會想到要問。

其中,限流是我特別追問的一項。我問 AI:「次數記在哪裡?部署到 Vercel 之後還有效嗎?」

而它採用的方式,是直接查詢資料庫裡,同一來源最近一小時已送出的回報數量,而不是把次數暫存在伺服器的記憶體裡。這樣一來,即使網站部署到不同的執行環境,也能共用同一份紀錄。

這不是唯一的限流方案,也有它的限制。但對目前這個不需要登入、送出後還會觸發 Telegram 通知的小功能,我決定先接受。

另外幾項防護也是一樣。有了 AI,增加功能很便宜,但複雜度並不免費。 多一條規則,就多一個需要理解、測試和維護的地方。

未來如果說回報問題變成要登入,好處就是我可以直接透過使用者來比對、計算回報次數,同時也能讓使用者看得到處理狀態;但缺點就會變成為登入訪客回報問題變得很麻煩。


五、實作

決定好之後,後續的實作幾乎都交給 AI。它照這個順序完成:

  1. 資料庫:新增一份 migration,建立 data_reports 資料表。這是新的一份,不是回頭改 Day 15 的那份,Day 15 說過,已經套用的 migration 不要回頭改。
  2. 後端:新增 /api/data-reports,負責檢查內容、存進資料庫、發通知。
  3. 通知:把 Telegram 的發送寫成獨立的檔案。
  4. 前端:在詳情頁加上回報按鈕和表單。

AI 也順手測了一件事:用公開金鑰去讀 data_reports,得到 401。訪客讀不到任何回報,跟 Day 16 的實驗 4 是同一扇門。

5.1 我自己做的事

這次要我自己處理的,是金鑰和 token:

  1. 取得 Supabase service role token: 進入 Supabase 專案介面 → 左側功能欄最下方「Project Settings」→ API Keys → service role keys → 找到 service role ( secret ),點擊 reveal 並複製貼到 .env.local

https://ithelp.ithome.com.tw/upload/images/20261001/201780175xA4NBUitV.png
【圖1|取得 Supabase service role key】

  1. 建立 Telegram bot:在 Telegram 找官方的 @BotFather,傳送 /newbot,依照指示分別取一個顯示名稱和帳號名稱(帳號名稱要以 bot 結尾)。完成後,BotFather 會給你一串 token,它等同 bot 的密碼,將其存入TELEGRAM_BOT_TOKEN的環境變數中(.env.local,要記得存檔)

https://ithelp.ithome.com.tw/upload/images/20261001/201780173WaVqVrxQl.png
【圖2|取得 Telegram BOT Token】

  1. 先跟 bot 說一句話:
    • 通知傳給自己:打開 bot 的對話,按下 Start,傳一則 /start
    • 通知傳到私人群組:把 bot 加進群組,傳 /start@你的bot帳號名稱
  2. 取得 chat id:chat id 代表「通知要送到哪個對話」。我請 AI 給我一行指令,它會透過剛剛存的TELEGRAM_BOT_TOKEN去問 Telegram 這個 bot 最近收到的訊息,並且只印出 chat id,不印出 token

幾個要注意的地方:

  • 管理者金鑰第一次登場。之前說過,它是給之後的管理功能用的,今天伺服器要用它把回報寫進資料庫。可能會看到它叫 service_role或是 secret key
  • 這些不能用 NEXT_PUBLIC_ 開頭,也不要貼進對話框

取得 chat id 這一步,我稍微卡了幾次。一次是複製指令時少了一個 /,Telegram 只回了一句 Not Found;另一次是指令裡的 ! 被終端機當成特殊符號。最後都是把錯誤訊息丟回給 AI 才找到原因。AI 給的指令沒問題,不代表你貼上去的也沒問題。

5.2 一個差點沒注意到的地方

回頭看 AI 的操作紀錄時,我發現它推上雲端用的是:

supabase db push --yes
  • -yes 的意思是「所有確認提示,都自動回答是」。

它有先預演,這次也只是新增一張表,沒有動到舊資料,所以結果沒有問題。但 Day 15 的分工表寫的是:推上雲端之前,要先讓我看過 SQL。 這次,我其實沒有看。

AI 很快就會把你訂的規則,當成「已經同意過了」。但之前允許過,不等於這次也不用看。 每次推上雲端前,還是值得看一眼:這次要執行的是哪幾份 migration,裡面寫了什麼。


六、驗收

6.1 嘗試繞過前端呼叫 API

功能做完了,接下來可以用 Day 16 學的 Postman(或是直接請 AI 嘗試),跳過畫面,直接呼叫自己的 API。這次我請 Codex 直接測試了一輪:

POST http://localhost:3000/api/data-reports
標頭:Content-Type: application/json

{
  "albumId": "armageddon",
  "versionId": "superbeing",
  "message": "Superbeing 版本的小卡數量可能有誤,請協助核對。",
  "website": "",
  "formStartedAt": 開啟表單的時間
}

website 就是 4.2 說的隱藏欄位,正常使用者看不到,所以會是空的;formStartedAt 是開啟表單的時間,用來判斷是不是瞬間送出。用 Postman 測試時,這兩個欄位也要一起帶上,不然會先被這兩關擋下來。

測試 狀態碼 回應
正常送出 201 成功保存,Telegram 收到通知
送空白的內容 400 請至少輸入 1 個字。
送超過 50 字 400 回報內容不可超過 50 個字。這就是繞過前端輸入框的實驗
送不存在的專輯 id 400 找不到這張專輯。
送不屬於這張專輯的版本 id 400 這個專輯版本不存在。
10 分鐘內送出一模一樣的內容 409 這份回報剛剛已經送出,不需要重複提交。
用公開金鑰直接寫入 Supabase 的 data_reports 401 permission denied for table data_reports
一小時內送第 6 則 429 回報次數過多,請一小時後再試。
故意把 Telegram token 填錯 201 回報照樣保存;通知狀態記成失敗,原因是「Telegram returned HTTP 401」

幾個值得注意的地方:

每一種錯誤,都有不同的狀態碼。 400 是送來的內容有問題,409 是跟已經存在的資料衝突,429 是送太頻繁,401 是沒有權限。昨天的那張狀態碼表,在這裡全部派上用場。

Telegram 失敗,回報並不會跟著不見。 token 填錯時,使用者一樣看到「已收到回報」,資料庫也記下了失敗的原因:Telegram 回了 401。這個 401 跟 Day 16 打 Supabase 時看到的是同一個意思:你還沒證明自己是誰,只是這次被拒絕的,是我們的伺服器。

使用者送出回報
      │
      ▼
/api/data-reports 檢查內容 ──── 不通過 → 400 / 409 / 429
      │ 通過
      ▼
① 存進 data_reports ─────────── 失敗 → 500,請使用者稍後再試
      │ 成功
      ▼
② 通知 Telegram
      ├── 成功 → 通知狀態記成 sent
      └── 失敗 → 通知狀態記成 failed,並記下原因
      │
      ▼
回 201:已收到回報(不管 ② 成功或失敗)

① 是主要流程,失敗了,回報就真的沒送出去,所以要讓使用者知道。② 是附加流程,失敗了,回報其實已經存好了,只是我沒收到通知,所以不該讓使用者重送。主要流程和附加流程,失敗時的處理方式可能會不一樣。

專輯不存在,回的是 400,不是 404。 AI 的理由是:/api/data-reports 這個網址本身是存在的,錯的是送來的內容裡,指向了一張不存在的專輯。如果把它看成「查詢一張專輯」,404 也說得通,但這是一支送出表單的 API,所以回 400。這呼應了 Day 16 的結論:狀態碼怎麼回,是設計 API 時的一個決定。

測試資料,記得清掉。 Codex 測完之後,自動把這次產生的 5 筆測試回報全部刪除了。這一步有時候會忘,但測試資料留在正式的資料表裡,之後就分不清哪些是真的回報。也因為限流是數資料表裡的回報,刪掉之後,限流的次數也跟著重置。

https://ithelp.ithome.com.tw/upload/images/20261001/20178017xO3DjKkfLj.png
【圖3|Codex 修改過程中自動執行的測試,確實有寫進資料庫】

Codex 測完後主動刪除了這次產生的五筆測試回報。這次它有辨識出自己建立的測試資料,刪除過程並沒有問題。但這邊要注意:就算是清理測試資料,只要操作的是共用或正式資料庫,也應該先確認刪除範圍。 AI 的工作不只是「完成」,還包含它認為完成之後應該順手處理的事。這些順手做的事,有時反而更值得檢查。
有些人會將測試環境跟正式環境的資料表分開,這樣的習慣也蠻好的,可以確保測試過程產生的資料不會污染正式環境的資料。

https://ithelp.ithome.com.tw/upload/images/20261001/20178017NnWmUsAL76.png
【圖4|Codex 刪除資料庫中測試資料過程中,有詢問是否確認刪除】

如果是自己用 Postman 測的時候要注意:限流是每小時 5 則,重複內容 10 分鐘內會被擋。測到一半一直被擋,先確認是不是被自己的限流擋住了,而不是功能壞了。

6.2 實際頁面

https://ithelp.ithome.com.tw/upload/images/20261001/20178017fSrcRaMGy3.png
【圖5|專輯詳情頁面多一個回報問題按鈕】

https://ithelp.ithome.com.tw/upload/images/20261001/20178017HK86ly4Hqr.png
【圖6|點擊會出現回報問題視窗】
(像這邊我實際測試,發現送出回報的按鈕,並沒有 hover 變色的效果,會是我後續想調整的地方。)

https://ithelp.ithome.com.tw/upload/images/20261001/20178017Jyq3nQGtrp.png
【圖7|隨意輸入送出後,出現回報成功】

https://ithelp.ithome.com.tw/upload/images/20261001/20178017ort8Swgpra.png
【圖8|資料庫有寫入回報資料】

https://ithelp.ithome.com.tw/upload/images/20261001/20178017vrgsbZQsIG.png
【圖9|Telegram 也有收到回報訊息】

還沒做的事

這幾項 AI 有建議,但這次先不做:

  • 機器人驗證(例如 Cloudflare Turnstile):限流擋得住手動亂送,擋不住大量換來源的機器人。等網站上線、真的遇到可以考慮再加
  • 通知失敗的重試:目前只會記錄「失敗」,還不會自動重送。
  • 回報管理介面:現在要看回報狀況,得去 Supabase 後台翻資料表

換個領域:同一條路,不同的產品

今天這條路,其實很多產品都有:

產品 使用者送出 後端檢查 存起來 通知
聯絡表單 姓名、問題 必填、長度、防機器人 訊息表 寄信給客服
點餐 餐點、數量 餐點存在、有沒有賣完 訂單表 通知廚房
預約 日期、時段 時段有沒有被訂走 預約表 通知店家

不管是什麼產品,順序都一樣:收進來、檢查、存起來、再通知。 而且檢查一定要在後端。


結語與明日預告

今天做的事,其實是我個人在 Vibe Coding 時代的開發順序:先讓 AI 把需求展開,再由人把它收回來。由 AI 實作,我負責做決定。

AI 很擅長把一個需求拆成前端、後端、資料庫、外部服務,也很擅長把該注意的事列出來。但要用哪一種通知、收不收個人資料、哪些預設可以接受,還是要由人決定。而要做這些決定,得先看得懂它在做什麼。


不過,今天的回報,不需要知道你是誰。任何人都能送,這個功能不要求使用者登入,也不收聯絡資料。但是如果像收藏專輯,就不一樣了。如果要讓每個人只看到自己的收藏,網站就得先回答一個問題:你是誰? 這時候就需要來實作登入功能了。

我們明天見。


上一篇
【Day 16|信號繩】CRUD 與 RESTful API:程式之間怎麼約定彼此說什麼
系列文
《我與 AI 的奇幻漂流:30 天,把「能跑」變成「能上線」》 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言