顯示具有 vibe coding 標籤的文章。 顯示所有文章
顯示具有 vibe coding 標籤的文章。 顯示所有文章

2026年9月12日 星期六

Google Antigravity 學習筆記 : 翻譯工廠

以前我會使用線上服務例如 onlinedoctranslator.com 將英文版 pdf 電子書翻譯成繁體中文版 (不能超過 10MB), 但如果來源檔不是 pdf (例如 epub) 就沒辦法, 參考 :


其實使用免費的 AI 工具像是 Antigravity 就可以因應書本題材選擇不同模型將 pdf/epub 電子書翻譯成繁體中文版, 可先轉成可編輯的 docx 檔, 然後再轉成 pdf. 

本系列全部文章索引參考 :


以下測試使用下列免費電子書 (英文版) 做為來源文本 :


下載網頁上的 "Pro Git" 這本書的 pdf 與 epub 檔後, 建立兩個專案目錄 pdf-ebook-translate 與 epub-ebook-translate, 分別將這兩個檔案搬移到各自的專案目錄下 : 




更多免費電子書資源參考 :



1. 系統架構 : 

我本來是想每次要翻譯時便建立一個專案來做, 但與 Gemini 討論後發現, 翻譯其實是一個經驗累積與不斷演進的過程, 每本書如果是用獨立的專案來翻譯, 那麼術語, 版式, 翻譯腳本等資產每次都要重新產生, 沒有辦法重複使用與演進, 應該建立一個翻譯工廠專案來處理所有電子書的翻譯工作, 只要有新的電子書要翻譯, 就將它複製到專案目錄下, 用提示詞叫 AI 翻譯即可.  


(1). 建立檔案目錄架構 : 

Gemini 建議的檔案目錄架構如下 :




但我們不用自己建這些檔案目錄結構, 只要先建一個專案目錄例如 book-translator-workspace 即可, 其餘可用提示詞叫 Antigravity 來做 : 

PS D:\antigravity_cli\projects> mkdir book-translator-workspace  
    目錄: D:\antigravity_cli\projects
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----       2026/9/10  下午 06:29                book-translator-workspace

然後切換到此專案資料夾下, 用 agy 指令啟動 Antigravity CLI : 

PS D:\antigravity_cli\projects> cd book-translator-workspace   
PS D:\antigravity_cli\projects\book-translator-workspace> agy   
Accessing workspace:
D:\antigravity_cli\projects\book-translator-workspace
Do you trust the contents of this project?
Antigravity CLI requires permission to read, edit, and execute files here.
> Yes, I trust this folder
  No, exit

然後將下列提示詞貼給 agy, 讓它建立上面的檔案目錄結構, 這是我叫 Gemini 自動產生的 :

請依照以下結構,幫我在當前工作區建立完整的目錄與檔案:
  1. 目錄與檔案清單:
    • shared/config/glossary.json(請初始化寫入 {},避免 JSON 解析錯誤)
    • shared/data/(快取資料庫目錄)
    • shared/core/(建立 __init__.py、llm_client.py、cache_manager.py、glossary_manager.py、docx_writer.py)
    • project_epub/input/、project_epub/output/ 與 project_epub/run_epub.py
    • project_pdf/input/、project_pdf/output/ 與 project_pdf/run_pdf.py
    • 根目錄下的 requirements.txt
  2. 建立原則:
    • 請透過終端機指令(如 mkdir -p、touch 等)建立好所有資料夾與檔案。
    • translation_cache.db 不需要手動建立空檔案,後續由 cache_manager.py 連線時自動生成即可。
    • 完成後請列出目前目錄樹(tree 或 find)讓我確認。



連續選 1. Yes 確認後它就會將檔案架構建好, 並用 tree 指令顯示結果 :

  ✅ 所有目錄與檔案建立完畢!以下是確認的結構:

    │
    ├───project_epub\
    │   │   run_epub.py
    │   ├───input\
    │   └───output\
    │
    ├───project_pdf\
    │   │   run_pdf.py
    │   ├───input\
    │   └───output\
    │
    └───shared\
        ├───config\
        │       glossary.json          ← 已初始化為 {}
        ├───core\
        │       __init__.py
        │       cache_manager.py
        │       docx_writer.py
        │       glossary_manager.py
        │       llm_client.py
        └───data\                      ← 快取資料庫目錄(translation_cache.db 將由程式自動生成)


(2). 安裝套件 & 實作程式模組 : 

上面已建立了系統架構, 但檔案內容都是空的, 接下來要叫 AI 填寫檔案內容與實作翻譯模組程式內容, 請 Gemini 生成如下提示詞 :

請幫我完成專案的環境安裝與核心程式碼實作:
  1. 安裝相依套件:
    在 requirements.txt 加入 google-genai、ebooklib、beautifulsoup4、python-docx,並在終端機執行安裝。
  2. 實作核心模組 (shared/core/):
    • llm_client.py:使用 Google GenAI SDK 呼叫 Gemini Pro 模型(請預設台灣繁中與技術專業設定,支援指數退避重試)。
    • cache_manager.py:使用 SQLite 維護 shared/data/translation_cache.db,以原文 MD5 為 key,支援中斷後接續翻譯。
    • glossary_manager.py:讀取 shared/config/glossary.json,比對段落關鍵字並注入 Prompt。請順便在 glossary.json 預填幾個常見 Git 術語(如 repository: 儲存庫, commit: 提交, branch: 分支, staging area: 暫存區)。
    • docx_writer.py:預設字型為『微軟正黑體』,支援標題層級、段落行距、表格排版,以及程式碼區塊(灰底 Consolas)。
  3. 實作 project_epub/run_epub.py:
    • 支援讀取 project_epub/input/ 目錄下的 EPUB 檔案。
    • 保留標題層級、正文、表格,特別注意:程式碼區塊(<pre> / <code>)請保持原文輸出不要翻譯。
    • 逐章處理並透過快取儲存,最後產出 .docx 至 project_epub/output/。
完成後請回報。

同樣一路連續選 1. Yes 確認後就會逐一實作程式並驗證, 最後結果如下 :





這樣就完成整個翻譯工廠專案的實作了, 但在最後面有提醒在開始進行 epub 電子書翻譯前要先設定模型的 API Key :

執行翻譯前請先設定 API Key:

    $env:GOOGLE_API_KEY = "你的金鑰"
    python project_epub/run_epub.py

將 .epub 放入  即可開始翻譯。

API Key 通常是放在 .env 隱藏檔裡, 在此專案的 shared/core/llm_client.py 中可用 dotenv 模組的 load_dotenv() 載入, 上面實作好的程式與 requirements.txt 檔也需要修改, 可以用下列提示詞叫 AI 去修改 : 

請在專案根目錄建立 .env 檔案(裡面預留 GEMINI_API_KEY=你的金鑰),並安裝 python-dotenv。同時在 shared/core/llm_client.py 開頭加入 load_dotenv(),讓程式啟動時會自動讀取 .env 裡面的 API Key。順便在 .gitignore 加上 .env 避免金鑰外洩。

一路連續選 1. Yes 確認讓 AI 去改, 完成後結果 : 




檢查專案目錄的根目錄下果然多了一個 .env 檔, 開啟後將自己的 API key 填入存檔, 就可以開始進行電子書翻譯了. 


2. 翻譯 epub 電子書 : 

將前面下載的 progit.epub 電子書複製到 project_epub/input/ 資料夾下面, 然後用下列提示詞叫 AI 先翻譯前 1~2 章來測試效果 :

我已經將 progit.epub 放到 project_epub/input/ 目錄下了。請先幫我執行翻譯前 2 個章節作為測試,產出 Word 檔到 project_epub/output/,並在終端機隨時回報進度。




但跑著跑著卻碰到免費帳戶每分鐘至多 5 次呼叫的上限 (注意, API 呼叫與訂閱 Google AI Pro 方案是兩個不同的帳戶, API 呼叫需綁信用卡購買 Pay-as-you-go 額度), 用下列提示詞叫 Claude 修改程式 : 

因為免費版 API 有每分鐘呼叫次數限制,請幫我在 llm_client.py 加入速率限制(Rate Limiter),每次呼叫間隔至少 30 秒,並在遇到 429 錯誤時自動等待 60 秒後重試,這樣我可以放著讓它免費跑完整本。

免費額度仍會因碰頂而收到 429 需等候 :




經過兩三次重跑, 終於完成前兩章的翻譯了 : 





開啟 output 資料夾下的 progit_ch01-02_zh.docx, 開啟後檢視內容, 我覺得翻譯品質很不錯 :




2026年8月18日 星期二

好書 : VIBE CODING 50 道零程式碼開發

前陣子從母校借到這本碁峰出版的好書, 篇幅不大, 但那 50 道小專案卻是練習 Vibe coding 技巧的絕佳素材 :





我打算一邊讀一邊用 Antigravity 來實作這些範例專案, 當作練習也好. 

2026年8月8日 星期六

Google Antigravity 學習筆記 : 安裝 Antigravity IDE 開發工具

本篇旨在紀錄如何在 Windows 安裝 Antigravity IDE, 本系列全部文章索引參考 :


Antigravity 是 Google 在 2025 年 11 月與 Gemini 3 一起推出的 Agent-First (代理人優先) AI 代理開發平台 (Agentic IDE), 基本上可以看成是 Vibe Coding 概念和 Gemini CLI 的 GUI 實現, 這是為 Vibe Coding 代設計的革命性工具, 它不再只是像 Copilot 那樣輔助寫程式, 而是讓 AI 成為一個可以獨立完成任務的代理人 (Agent). Antigravity 最大的特色在於它不只是幫忙補全代碼, 而是幫我們 "把事情做完".   

作為新一代 IDE, Antigravity 與傳統 IDE (例如 VS Code 或 Cursor) 不同, Antigravity 的核心理念是 "擺脫重力", 亦即擺脫繁瑣的底層編碼工作, 讓開發者從碼農升級為指揮家. Antigravity 是基於 VS Code 構建而成, 但它將介面一分為二, 引入了多代理人協作 (Multi-Agent Orchestration) 概念, 將 IDE 從編輯器轉變為任務控制中心 (Mission Control), 這是 Antigravity 最具辨識度的功能, 它讓開發者不再只是面對程式碼編輯器, 而是從編輯者視角 (Editor View) 提升為代理人經理視角 (Agent Manager), 像專案經理一樣發號施令,  例如 "幫我做一個能計分的貪食蛇遊戲", 然後就看著 AI Agent 自動規劃, 建立檔案與編寫程式, 而且還可以同時指揮多個 Agent 平行處理不同任務. 

Antigravity 的 Agent 不只會寫程式, 它還擁有視覺和操作能力, 它超越 VS Code/GitHub Copilot/Cursor 的一大亮點是內建了一個受控的 Chrome 瀏覽器代理, Agent 在寫完網頁 App 會自動打開這個瀏覽器載入此網頁, 點擊按鈕與輸入數據自動進行測試. 如果有錯誤或跑版, Agent 會看到這些錯誤訊息, 然後自動回到編輯器修正程式碼, 形成一個自主的 "編碼 > 測試 > 修正" 閉環. 為了讓開發者敢放手給 AI 做專案, Antigravity 不會馬上丟出一堆程式碼, 而是會先生成所謂的 Artifacts, 裡面包含任務清單 (Task Lists) 與實作計畫 (Implementation Plans), 任務清單是 Agent 打算要採取的步驟, 需要開發者批准. 完成專案後 Antigravity 還會提供測試截圖或瀏覽器操作錄影, 證明它真的跑過且成功了. 

Antigravity IDE 下載網址 :





按 Downlowad 鈕, 選擇自己的作業系統下載安裝檔 : 




Windows 系統會下載 Antigravity-x64.exe 0 檔 (約 134 MB), 點擊安裝首先會出現安全性提醒, 按底下的 Next : 




勾選主題背景, 用預設的 System 即可, 按 Next : 




出現詢問是否要預先安裝 Google 相關開發工具的擴充插件 (Plugins) 頁面, 這些插件包含各種技能 (skills) 與 MCPs (Model Context Protocol), 目的是讓 Antigravity 裡面的 AI Agent 能更好地協助我們使用 Google 的各種開發產品, 保持預設全空即可, 點擊底下的 Finish 完成安裝. 日後如果有需要隨時可以在設定 (Settings) 裡面補充安裝 :




完成後頁面中間顯示預設模型為 Gemini 3.6 flash, 點擊會顯示可選用的模型 : 




右上角有一個 Install IDE 按鈕用來安裝 Antigravity 完整版桌面 IDE 介面 (約需要 1 GB 至 2 GB 的硬碟空間, 包含微軟 VS Code 底層約 300 MB~500 MB, 擴充套件與 AI 依賴組件約 500 MB~1 GB). 目前開啟的是 Antigravity IDE 的輕量圖形化 Agent 控制台交談對話介面, 沒有程式碼編輯器, 須點擊 Install IDE 鈕下載安裝完整版 IDE, 才會有基於 VS Code 核心打造的程式碼編輯器環境. 如果只是要利用 AI Agent 進行對話生成程式碼, 並搭配外部慣用的其它編輯器 (例如 Thonny) 使用, 則不安裝完整版 IDE 也不會影響目前的對話與 Agent 功能. 

上面所安裝的輕量版 Antigravity IDE 可以看成是 Antigravity CLI 的 GUI 版本, 兩者底層都呼叫相同的 Antigravity Agent 與模型, 都能對專案資料夾進行讀寫, 執行指令, 建立檔案, 與重構程式碼. 兩者核心運作邏輯幾乎一樣, 最主要的差別在於 GUI 介面與 CLI 文字終端機的互動體驗而已, 不過  IDE 還多了幾項便利功能 : 
  • 專案與對話管理 :
    GUI 提供視覺化的專案清單 (Projects), 歷史對話紀錄 (Conversation History), 在切換專案或歷史對話方面 IDE 比 CLI 更直觀. 
  • 視覺化互動 :
    在 GUI 中可以隨時用滑鼠切換模型 (例如畫面中的 Gemini 3.6 Flash), 點選工具選項或直接用麥克風語音輸入, 不用像 CLI 一樣記指令或參數. 
  • 擴充與 IDE 整合 :
    按 GUI 版右上角的 Install IDE 按鈕能一鍵升級成完整的 VS Code 開發環境, CLI 則主要是搭配現有的編輯器 (如 Thonny, VS Code, Cursor 等) 在背景或終端機旁運作.
  • 內建 Terminal 終端機 :
    按 GUI 版右上角的 Install IDE 按鈕安裝完整版 IDE 後, 按快捷鍵 Ctrl + ~ 即可開啟與系統完全同步的 Terminal, 可隨時手動輸入 curl, python, git 等任何系統命令.  
總之, 習慣直接在視窗介面上看歷史紀錄, 點選專案進行 Vibe Coding, 用 GUI 會更方便; 若習慣在 Terminal 開著腳本直接批次跑, 則 CLI 比較輕巧好用. 

2026年8月1日 星期六

高科大還書 4 本 (AI超神應用術 + Vibe Coding 聖經等)

由於有四本預約書到館 : 




額度已經滿檔, 拿下面四本書去換 :
No.1 與 No.2 屬於基礎書籍目前較少看, 且市圖可借到; No.3 與 No.4 被預約須還, 但還沒看完, 下次再回借 (尤其是 No.4 這本雖然市圖也有, 但是超熱門). 

好書 : Vibe Coding 聖經

此書前陣子從母校圖書館借得, 樂讀中. 作者 Steve Yegge 與 Gene Kim 都是具有程式設計背景的老程式人, Steve 曾在 Google 與 Amazon 服務, 是典型的軟體業老兵; 而 Gene 則是在十餘年的技術職涯後轉職主管, 從此離開技術圈, 後來成為華爾街暢銷書作者. 兩人都在接觸 Vibe Coding 後重回軟體開發之路, 但這回不再有老程式人的磨難記憶, 而是從廚師晉身為行政主廚, 此書正是他們運用 Vibe Coding 重拾軟體開發樂趣的經驗之談. 





以下是我的閱讀札記 :

第一章 : 未來已經來臨, 程式設計的重大轉變正在發生
  • Vibe coding 的好處 : 讓你的創作更快速, 更具野心, 更自主, 也更有趣, 也能探索更多選項. 這就是所謂的 FAAFO (Fast, Ambitious, Fun, Optionality). AI 的速度與知識力讓原本依靠人力看似不可能的專案變得有希望實現, 使我們對開發更具有野心; 獨自與 AI 一起工作顯著地減少了兩項昂貴的成本 : 協調成本 & 溝通成本. 
  • 以往的軟體工程師花了太多時間與新框架新套件, 無暇解決那些真正有趣的問題. Vibe coding 能讓你跳過這些繁雜的事物, 專注處理重要的事情. 你從此可以透過 AI 寫出各式各樣的出色軟體, 也終於可以展開那些在 "有空來做" 清單上目標遠大的專案. 
  • Vibe coding 可以讓你跳過冗長的教學和基礎設定, AI 會處理所有麻煩的阻礙, 包括建置環境. 你再一次有能力做出一些有用的東西, 不會被複雜的實作細節給活埋. 有了 Vibe coding 後, 這世界有一整個世代的退休工程師正帶著復仇的決心歸來, 準備向全世界展現他們的實力. 
  • AI 輔助的程式設計不只是線性成長, 更像是指數級飛躍的革命.
  • Vibe coding 能讓你解決過去認為不可行的專案, 一個人完成以往需要團隊才能辦到的創舉, 重新找回軟體開發的樂趣. 借助 LLM 我們能進行複雜而深層的討論, 用自然語言對話解決複雜問題, 這曾經完全是科幻的情節, 如今則是日常的現實.
  • 不論是聊天式或代理式的程式設計都能從 LLM 獲得強大無比的能力, 在未來的世界, 只要能清楚解釋自己的需求, 這些自然語言就能成為可運行的軟體. 在 AI 時代, 最需要培養的能力是敘事力 (narrative ability). 
  • AI 做的並非只是產出程式碼而已, 它是一個溝通成本極低的合作夥伴, 它會幫助你集思廣益, 評估選項, 管理專案與團隊, 應對挑戰, 以及制定策略來實現你的目標與抱負. 
  • AI 程式設計工具正在重塑軟體開發的基礎, 工程師的角色會更像是產品工程師, 根本性的角色變化是從寫程式碼轉換至音引導 AI. 軟體工程師必須體認到, 該是時候放棄手刻每一行程式碼了, 要完全擁抱這的打造軟體的新方法. 在這個新世界中, 你就是世界級廚房的主廚, 你不需要親自切菜, 煎肉, 與洗盤子, 你有 AI 這個副主廚與一整個團隊來處理這些事. 
  • 用 AI 寫程式有一個原則 : 你必須概括承受一切責任, 交給 AI 實作不代表交給 AI 負責. 在 Vibe coding 中你必須負責 :
    • 管理平行開發
    • 處理複雜的整合問題
    • 制定標準
    • 建立導入程序
    • 協調大型專案
第二章 : 程式設計只有贏家, 沒有倖存者
  • Vibe coding 會大大提升我們的抽象層級, 將我們從無關緊要的底層細節 (函式庫, 框架, 語法 ...) 中解放出來, 讓我們能更自然地表達想法, 專注在更高階的問題. 但這並不表示 Vibe coding 很簡單, 你的判斷力與經驗比以往更加重要了. 太空人 Frank Borman 曾說 : "優秀的飛行員會用卓越的判斷力來避開應用到卓越飛行技術的情況", 這正是軟體工程師的過往訓練在 AI 時代的價值所在. 此外, 你還需要培養液種新的直覺, 才能理解 LLM 和程式碼之間發生了甚麼事. 
  • 如果把軟體製作比喻為拍電影, 我們以前是編劇, 現在則是負責指引畫面的導演, 讓協作的 AI 負責處理實作的細節.
第三章 : Vibe coding 的價值
  • 把 FAAFO 當成你新的超能力, 你可以更快地產出程式碼, 而且能大膽嘗試那些過去只能一笑置之的專案. 你可以一個人自主完成過去需要一個團隊才能處理的工作. Vibe coding 會重塑我們能力可及的範圍, 讓我們能懷抱更大的野心. 以前覺得太不實際的點子, 現在可以毫不猶豫地丟進待辦清單裡. 
  • 速度 (Fast) 是 Vibe coding 最明顯的一項價值, 可說是最表面的好處, 但速度的真正價值在於它放大了 FAAFO 其他面向的價值. 
  • 注意. Vibe coding 的優點 FAAFO 裡面並不包括品質 (Quality), 那是你自己的責任. 
  • 使用 Vibe coding 時需要學會觀察 AI 是不是充滿自信地走錯了路, 以決定是否該換個方向, 或是放棄成效不佳的專案. 
  • 傳統的程式設計包含很多沒甚麼人會喜歡的瑣碎工作, 例如修改語法錯誤, 檢查型別, 與不熟悉的套件管理工具纏鬥, 寫樣板程式碼, 以及翻找文件等. Vibe coding 能排除這些痛苦, 讓注意力從實做細節回到發揮創意上. 程式設計師使用 Vibe coding 後會感到比以往更有開發的熱情, 壓力驟減. 
  • Vibe coding 降低了平行探索各種路徑的成本, 你可以任意挑選一個程式語言來開發專案, 本來需要數天的評估研究, 現在 AI 能擠壓到幾分鐘就能提供每個選項的利弊, 而且不必寫任何一行程式碼. 這是程式設計師以往從未擁有的奢侈能力, 可同時嘗試多種不同做法, 而且是零成本. Vibe coding 改變了軟體設計的經濟學.
第四章 : 黑暗面-Vibe coding 的大失誤
  • 用 AI 寫程式可能帶來系統性風險, 造成的問題也會比傳統軟體開發更嚴重. 最常見的問題是, AI 可能會在遵循指示上出現困難, 尤其是當 contex window 滿了之後. 如果缺乏適當的監督, AI 可能從強大的生產設備變成最可怕的噩夢. 
第五章 : AI 正在改變所有知識型工作
  • AI 對軟體業帶來的改變正如漣漪般擴散到所有知識型工作, 研究顯示最容易被影響的是高薪職業, 例如數學家, 稅務師, 金融分析師, 作家, 以及網頁設計師等等. 掌握大局觀是在其中找到自身出路的關鍵.
  • 專家研究指出只有 34 種 "以原子移動為主" 的職業在 AI 時代是安全的, 例如 : 機車技師, 快餐廚師, 地板打磨工等, 這些職業需要靈巧的手工與即時的反應操作, LLM 無法提供這方面協助. 
  • 軟體工程師是 AI 海嘯的第一排受害者, 我們可能是最後一代手刻程式碼的開發者, 但 ... 至少玩得開心! 
  • AI 時代的職場新面貌是, 在公司裡本來需要等待工程師幫忙的地方都會開始自行 Vibe coding, 過去的選擇只有苦等或外包, 或是找上層介入, 而現在所有人都可以自己製作軟體了: 建立原型, 修復問題, 甚至開發新功能. 
  • 在所有知識工作者都開始自己 Vibe coding 時, 軟體工程師依舊扮演著非常重要的角色, 只是和過去扮演的不同而已. 在特別需要穩定性, 安全性, 或器爺及擴展性的領域, 專業的軟體工程師依然不可或缺. 
  • 只要門檻夠低, 軟體開發將成為另一種表現創意的形式, 會有更多人利用 AI 不寫任何一航程式碼而創作出新軟體. 軟體開發的速度從一年降低為一周後, 能源, 製造, 醫療, 教育的成本都會同時下降, 新的產品與服務會快速出現, 新的創新發明刺激更多需求, 經濟產出也會一飛沖天. 
第八章 : 歡迎來到 Vibe coding 廚房
  • Vibe coding 不需要記憶成是語法或深奧指令, 真正關鍵的能力是和你的 AI 夥伴有效溝通. 
第十章 :
  • 使用 AI 的工作空間時要注意的關鍵技巧 : 
    • 留意 AI 的上下文窗口
    • 及早發現上下文飽和
    • 使用重點式或完整式的上下文
    • 尋找工具支持
    • 相信直覺


~進行中~

PS : 此書被預約須還, 目前已看到第 12 章, 以後回借再繼續看完. 

2026年7月22日 星期三

Google Antigravity 學習筆記 : 重構 serverless 函式執行平台 (六)

前一篇在 Mapleboard 上成功地佈署了新版 serverless 平台, 驗證 API Key 功能正常, 整個專案重構來作業已近尾聲, 本篇要將專案提交上傳更新 GitHub 上的 repo (儲存庫). 

本系列全部文章索引參考 :



8. 提交新版 serverless 與上傳 GitHub : 

在提交之前, 我回顧了前面的測試過程, 發現剛開始時是從 https://github.com/tony1966/serverless.git 複製專案到 D:\antigravity_cli\projects 底下, 然後將其打上 tag 標記後建立一個新分支 api-token-auth, 再叫 agy 用 Claude 模型去重構此專案, 這就形成了 Nested Repository (巢狀儲存庫) : serverless 資料夾內自成一國 (裡面有自己的 .git), 而外層的 antigravity_cli 也有自己的 .git 現象. 

站在 D:\antigravity_cli 的角度看 projects/serverless 時, Git 預設只會把 serverless 當成一個被忽略的子目錄或是一個未追蹤的 gitlink, 兩邊的 Commit 完全是分開互不干擾的. 在 serverless 下提交並 push 時會上傳到 https://github.com/tony1966/serverless 上, 而非 Antigravity CLI 的 repo 庫https://github.com/tony1966/antigravity_cli. 雖然這種國中有國的情況並不少見, 但我想讓專案資料夾與儲存庫的關係單純點, 所以將此 serverless 專案剪下來貼到 D:\ 下, 與 D:\antigravity_cli\projects 分家. 

分家後的 serverless 也要有自己的 .gitignore, 所以我把 Antigravity CLI 大倉庫下的 .gitignore 複製一份來給它用 : 

# ==========================================
# Python 相關快取與編譯檔案
# ==========================================
__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
.poetry/
.venv/
venv/
ENV/
env/

# ==========================================
# 專案實務資料與測試產出
# ==========================================
*.json
*.db
*.log

# ==========================================
# 開發工具與編輯器暫存檔
# ==========================================
.vscode/
.idea/
*.swp
*.bak
.DS_Store
Thumbs.db

# ==========================================
# 敏感資料與環境變數 (絕對不能上傳)
# ==========================================
.env
.env.local
.env.*.local
*.env

分家後的 serverless 專案結構如下 :

PS D:\> tree serverless /f   
列出磁碟區 新增磁碟區 的資料夾 PATH
磁碟區序號為 0000027B 1258:16B8
D:\SERVERLESS
│  .env
│  .gitignore
│  Procfile
│  requirements.txt
│  serverless.db
│  serverless.py
│  serverless_error.log
│
└─functions
    │  add_function.py
    │  add_table.py
    │  clear_stats.py
    │  delete_function.py
    │  delete_record.py
    │  drop_table.py
    │  edit_function.py
    │  execute_sql.py
    │  export_table.py
    │  hello.py
    │  list_functions.py
    │  list_tables.py
    │  save_function.py
    │  show_schema.py
    │  show_stats.py
    │  update_function.py
    │  view_table.py
    │  __init__.py
    │
    └─apps_backup
          add.py
          hello.py
          linebot_gemini.py
          linebot_gpt.py
          send_books_messages.py
          send_ksml_books_messages.py
          update_ksml_books.py

這樣便可進行 Git 提交作業了, 首先切換到專案目錄下 :

S D:\> cd serverless   

然後用 git add . 指令將所有變更加入暫存區, 把修改過的程式碼與新增的 .gitignore 檔加入追蹤 (.gitignore 內所舉的檔案除外) : 

PS D:\serverless> git add .   
warning: in the working copy of 'functions/save_function.py', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'functions/update_function.py', LF will be replaced by CRLF the next time Git touches it
PS D:\serverless>

報出的 warning 可以忽略. 

然後建立 Commit 紀錄, 將變更封裝並附上明確的功能說明 : 

PS D:\serverless> git commit -m "feat: implement serverless API core with API Key auth"   
[feature/api-token-auth df845ce] feat: implement serverless API core with API Key auth
 5 files changed, 172 insertions(+), 37 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 functions/hello.py

接下來是推送到 GitHub 遠端倉庫的 feature/api-token-auth 分支 (-u 參數會自動建立本地與遠端分支的追蹤關係) :

PS D:\serverless> git push -u origin feature/api-token-auth   
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 16 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (18/18), 8.71 KiB | 1.24 MiB/s, done.
Total 18 (delta 7), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (7/7), completed with 4 local objects.
remote:
remote: Create a pull request for 'feature/api-token-auth' on GitHub by visiting:
remote:      https://github.com/tony1966/serverless/pull/new/feature/api-token-auth
remote:
To https://github.com/tony1966/serverless.git
 * [new branch]      feature/api-token-auth -> feature/api-token-auth
branch 'feature/api-token-auth' set up to track 'origin/feature/api-token-auth'.

可見 18 個檔案物件已成功打包上傳 (其中包含新的 .gitignore 與 code), 且在 GitHub 成功建立獨立的 feature/api-token-auth 分支與追蹤關係. 這時到 GitHub 去察看此 repo :




可見 GitHub 遠端倉庫 tony1966/serverless 已經順利接收到了最新的變更, 而下方的是 main 分支 (正式生產環境) 的檔案列表, 顯示它依然維持 6 個月前的穩定版本, 只要此 feature/api-token-auth 分支尚未合併到 main 分支就不會影響目前依賴此 repo 的線上服務 (例如 render.com). 

目前暫時先做到這裡, 後續若準備好要把 API Key 驗證功能部署到 Render.com 時, 只要按右上角的 "Compare & pull request" 鈕, 檢查變更內容沒問題後按 "Create pull request" 鈕, 再按 "Merge pull request" 與 "Confirm merge" 鈕即可合併的 main 分支, 完成升版的最後一塊拼圖. 

如果要在終端機下指令合併, 順序如下 : 

# 切換到 main 分支
git checkout main

# 拉取遠端最新 main(確保同步)
git pull origin main

# 將 feature 分支合併進 main
git merge feature/api-token-auth

# 推送回 GitHub main 分支(會觸發 Render 自動部署)
git push origin main

2026年7月19日 星期日

Google Antigravity 學習筆記 : 重構 serverless 函式執行平台 (五)

經過前面的測試已驗證了重構後的程式碼基本上達成了讓平台具備 API Key 功能的目標, 在提交到 GitHub 之前, 想要先佈署到 Mapleboard 上更新 serverless 到具有 API Key 功能的最新版. 關於 Mapleboard 筆記參考 :


本系列全部文章索引參考 :



7. 在 Mapleboard 佈署新版 serverless : 

先用 VNC Cloud 遠端連線到 Mapleboard 桌面, 開啟終端機, 切換到 serverless 專案目錄下, 用 zip 指令把整個專案除指定之敏感檔案外全部壓縮成 zip 檔 : 

tony1966@LX2438:~/flask_apps/serverless$ zip -r serverless_v4.zip . -x "serverless.db" "serverless_error.log" "*.pyc" "__pycache__/*" ".env"

參數 -r 表示要遞迴壓縮, 即連同所有子資料夾 (如 functions/) 一起打包. serverless_backup.zip 是壓縮結果的檔檔名. 後面的 . . 代表壓縮當前目錄下的所有東西, -x 參數後面接的是排除 (不壓縮) 名單, 這裡排除了本地資料庫 (serverless.db), 日誌檔 (serverless_error.log), Python 快取檔, 以及含有敏感密碼的 .env 檔. 

然後用 WinSCP 連線 Mapleboard, 注意, 之前安裝 fail2ban 時為了提升資安, 已修改 SSH 埠 (不再是預設的 22), 埠號可用下列指令查得 :

tony1966@LX2438:~/flask_apps/serverless$ sudo nano /etc/fail2ban/jail.local  

設定 WinSCP 連線時除了要輸入固定 IP 外, 還要更改 SSH 埠號, 如果用 22 埠是無法連線的. 連線成功後, 先將上面備份的 zip 檔傳送至本地保存. 完成後將本地的 serverless.py 主程式與 functions 資料夾上傳到 Mapleboard 的 serverless 專案目錄下覆蓋舊版程式檔. 

由於主程式 serverless.py 有更改, 所以須用下例指令重啟服務才會運行新版程式 :

tony1966@LX2438:~/flask_apps/serverless$ sudo systemctl restart serverless

用瀏覽器測試 hello.py 函式 : 





在有登入情況下, 對函式的請求無需攜帶 API Key 即可順利執行;  如果沒有登入就會收到 401 錯誤, 例如 :



但修改前一篇的 deploy.py 的 URL 想要進行本地佈署 hello.py 卻出現連線異常與 404 等錯誤, 經查原來我的 Mapleboard 之前在架站時 Nginx 與站台 (主要是 hello 站台與 flask.tony1966.cc) 設定出現埠的衝突, 重啟 Nginx 出現 2~3 個 warning. 經過與 Gemini 討論後, 修改站台設定後終於解決此問題, Nginx 設定檔就徹底理順了, 摘要如下 : 
  • reject_ip 盡職地守在大門口擋掉所有奇奇怪怪的 IP 掃描.
  • flask.tony1966.cc 安全地走 HTTPS 連線, 讓新版 Serverless 平台, 樹莓派爬蟲與電腦部署都能順暢通訊. 
  • hello 站台也成功轉型, 乾乾淨淨地留在 443 埠當作你的 is_alive 測試通道 (Port 8080/8081).
站台清單檢視指令 : ls -l /etc/nginx/sites-enabled/
重啟 Nginx 伺服器指令 : sudo nginx -t
重新載入 Nginx 指令  : sudo systemctl reload nginx

佈署程式 deploy.py 的 URL 要改成 https://flask.tony1966.cc, 下面是佈署市圖爬蟲伺服端程式 update_ksml_books.py 的範例 (伺服端程式都不必為 serverless 升版做任何改變) : 

SERVER_URL="https://flask.tony1966.cc"
API_TOKEN="your-api-key"

# 2. 定義要佈署的函式名稱與本地檔案路徑
TARGET_FUNCTION_NAME="update_ksml_books"
LOCAL_FILE_PATH="update_ksml_books.py"

再次執行 deploy.py 就順利將函式發佈到 serverless 平台上了 :

D:\python\test>python deploy.py  
🚀 正在將 update_ksml_books.py 更新至 https://flask.tony1966.cc...
✅ 函式更新成功!
伺服器回應: {'func_name': 'update_ksml_books', 'message': '模組 update_ksml_books 已成功更新'}

deploy.py 的完整內容如下 :

# deploy.py
import os
import requests

# 1. 設定伺服器資訊與 API Token
# 地端測試可用 http://127.0.0.1:5000,上雲端後改成你的 Render 網址
SERVER_URL="https://flask.tony1966.cc"
API_TOKEN="your-api-key"

# 2. 定義要佈署的函式名稱與本地檔案路徑
TARGET_FUNCTION_NAME="update_ksml_books"
LOCAL_FILE_PATH="update_ksml_books.py"

def function_exists(func_name):
    """檢查遠端伺服器上的函式是否已存在(透過 GET 探測)"""
    url=f"{SERVER_URL}/function/{func_name}"
    headers={"X-API-Key": API_TOKEN}
    try:
        response=requests.get(url, headers=headers)
        # 404 表示不存在,其他狀態(200/400/500)表示檔案存在
        return response.status_code != 404
    except requests.exceptions.RequestException:
        return False  # 連線失敗時保守假設不存在

def deploy_function():
    # 檢查本地檔案是否存在
    if not os.path.exists(LOCAL_FILE_PATH):
        print(f"❌ 找不到本地檔案: {LOCAL_FILE_PATH}")
        return
    # 讀取本地最新的程式碼內容
    with open(LOCAL_FILE_PATH, "r", encoding="utf-8") as f:
        new_code=f.read()

    headers={
        "X-API-Key": API_TOKEN,
        "Content-Type": "application/json"
    }
    payload={
        "func_name": TARGET_FUNCTION_NAME,
        "code": new_code
    }

    # 3. 自動判斷:函式已存在 → update,不存在 → save(新增)
    if function_exists(TARGET_FUNCTION_NAME):
        url=f"{SERVER_URL}/function/update_function"
        action="更新"
    else:
        url=f"{SERVER_URL}/function/save_function"
        action="新增"

    print(f"🚀 正在將 {LOCAL_FILE_PATH} {action}至 {SERVER_URL}...")
    try:
        response=requests.post(url, json=payload, headers=headers)
        # 新增成功是 201,更新成功是 200
        if response.status_code in (200, 201):
            print(f"✅ 函式{action}成功!")
            print("伺服器回應:", response.json())
        else:
            print(f"❌ {action}失敗 (狀態碼: {response.status_code})")
            try:
                print("錯誤原因:", response.json())
            except ValueError:
                print("非 JSON 回應內容:", response.text)
    except requests.exceptions.RequestException as e:
        print(f"💥 連線發生異常: {e}")

if __name__ == "__main__":
    deploy_function()

市圖爬蟲程式本機版則需要配合 serverless 添加 API Key 功能而升版, 否則請求都會被 401 拒絕, 這部分記在另一篇. 

2026年7月18日 星期六

Google Antigravity 學習筆記 : 重構 serverless 函式執行平台 (四)

前面的測試已驗證了重構後的程式碼基本上達成了讓平台具備 API Key 功能的目標, 本篇要來測試本地自動化佈署功能, 在此過程中發現了原系統的一些缺陷, 都利用 agy 請 Claude Sonnet 修正, 除了主程式 serverless.py 外, 還包括 functions 下的 update_function.py 與 save_function.py. 

本系列全部文章索引參考 :



6. 本地自動化佈署 : 

第一版的 serverless 系統在新增或修改函式時都需登入平台, 透過手動操作網頁來完成, 在本次重構加上 API Key 機制後, 系統安全性增強, 可以在本地撰寫一個自動化佈署程式, 攜帶 API Key 經過驗證完成函式的佈署. 只要寫一個 Python 函式讀取要佈署的函式檔, 然後攜帶 API key 呼叫 update_function 或 save_function 即可完成函式的佈署. 


(1). 與 Claude 討論 :

> 我執行下列佈署程式 :
  # deploy.py
  import os
  import requests

  # 1. 設定伺服器資訊與 API Token
  # 地端測試可用 http://127.0.0.1:5000,上雲端後改成你的 Render 網址
  SERVER_URL="http://127.0.0.1:5000"
  API_TOKEN="new-test-token"

  # 2. 定義要更新的函式名稱與本地檔案路徑
  TARGET_FUNCTION_NAME="hello"
  LOCAL_FILE_PATH="hello.py"

  def deploy_function():
      # 檢查本地檔案是否存在
      if not os.path.exists(LOCAL_FILE_PATH):
          print(f"❌ 找不到本地檔案: {LOCAL_FILE_PATH}")
          return
      # 讀取本地最新的程式碼內容
      with open(LOCAL_FILE_PATH, "r", encoding="utf-8") as f:
          new_code=f.read()
      # 3. 準備發送給 update_function 的端點與資料
      # 路由設計是動態載入的 /function/update_function
      url=f"{SERVER_URL}/function/update_function"
      headers={
          "X-API-Key": API_TOKEN,
          "Content-Type": "application/json"
          }
      payload={
          "func_name": TARGET_FUNCTION_NAME,
          "code": new_code
          }

      print(f"🚀 正在將 {LOCAL_FILE_PATH} 部署至 {SERVER_URL}...")
      try:
          # 發送 POST 請求更新遠端函式
          response=requests.post(url, json=payload, headers=headers)
          # 檢查伺服器回應狀態
          if response.status_code == 200:
              print("✅ 函式更新成功!")
              print("伺服器回應:", response.json())
          else:
              print(f"❌ 更新失敗 (狀態碼: {response.status_code})")
              try:
                  print("錯誤原因:", response.json())
              except ValueError:
                  print("非 JSON 回應內容:", response.text)
      except requests.exceptions.RequestException as e:
          print(f"💥 連線發生異常: {e}")

      deploy_function()
  ===
  出現如下錯誤 :
  D:\python\test>python deploy.py
  🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
  ❌ 更新失敗 (狀態碼: 401)
  錯誤原因: {'error': 'Authentication required', 'login_url': '/login'}
  ===
  在提供有效 API Key 情況下應該要能通過不受 login 阻攔, 請修改

▸ Thought for 29s
  The user is experiencing a 401 error with the message "Authentication required" ...
  問題很清楚。看一下目前的驗證順序:

● Read(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  根本原因找到了:

  步驟 1(L165)先攔截, update_function  在  PROTECTED_FUNCTIONS  裡,API Client 沒有 Session →
  直接 401,根本輪不到步驟 2 的 Token 驗證。

  解法:將兩層驗證合併,一次判斷 Session 或 Token 擇一通過,同時可以移除  TOKEN_PROTECTED_FUNCTIONS
  (已不需要):

● Read(D:/antigravity_cli/projects/serverless/serverless.py)
  if __name__ == "__main__":
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  完成。來驗證最終結果:

● Read(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  還有一個小問題:L184 的步驟編號  # 3.  與 L182 的  # 3.  重複了,順手修正:
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  完成。以下是這次修改的完整說明:
  ──────
  ## 問題根源

    原本流程(有 Bug):
      步驟 1:update_function 在 PROTECTED_FUNCTIONS? → 是
               check_auth() = False(API Client 無 Session)
               → 直接 401 ❌ (Token 驗證根本輪不到)

  ## 修正後的流程
    新流程(handle_function 前三步):

    步驟 1:一次性完成雙重驗證
            is_session_auth = check_auth()
            if 無 Session → 嘗試讀 X-API-Key → 查資料庫 → is_token_auth
            is_authorized = is_session_auth OR is_token_auth

    步驟 2:管理模組 → 需要 is_authorized,否則 401
    步驟 3:一般函式 → 需要 is_authorized,否則 401

  ## 各情境結果

   呼叫者                      | Session              | API Key              | 結果
  -----------------------------|----------------------|----------------------|----------------------
   瀏覽器管理者(update/save) | ✅                   | ❌                   | ✅ 放行
    deploy.py  API Client      | ❌                   | ✅ 有效              | ✅ 放行
   未認證請求                  | ❌                   | ❌                   | ❌ 401
   無效 Token                  | ❌                   | ❌ 無效              | ❌ 401
────────────────────────────────────────────────────────────
> 成功了, 但出現連線異常:
  D:\python\test>python deploy.py
  🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
  ✅ 函式更新成功!
  💥 連線發生異常: Expecting value: line 1 column 1 (char 0)
  這要修改 deploy.py 還是系統的 update_function.py 較好?
▸ Thought for 27s
  The user is seeing a successful update (✅) but then a connection error. Let me...
  先看清楚錯誤發生在哪一行:

   deploy.py  收到 200 → 印出 ✅ → 再執行  response.json()  時爆炸,代表  update_function.py
  回傳的是 HTML 而非 JSON。

  讓我確認  update_function.py  的完整內容:

● Read(D:/antigravity_cli/projects/serverless/functions/update_function.py) (ctrl+o to expand)

  問題找到了,而且有兩個 bug 同時存在:

   # | 問題         | 說明
  ---|--------------|-------------------------------------------------------------------------------
   1 | 欄位名稱不符 |  deploy.py  送  func_name ,但  update_function.py  讀  module_name  →
     |              | 讀到空字串
   2 | 輸入格式不符 |  deploy.py  送 JSON body,但  update_function.py  只讀  request.form  →
     |              | 永遠讀不到資料
   3 | 回應格式不符 | 成功時回傳 HTML, deploy.py  呼叫  response.json()  爆炸
  ──────
  ## 修哪個?

  修  update_function.py ,理由:

  • 這是 server 端的問題(不認識 JSON 輸入、不回傳 JSON)
  • 瀏覽器用表單呼叫、API Client 用 JSON 呼叫,server 應該兩者都支援
  •  deploy.py  的設計是正確的,不需要改
● Edit(D:/antigravity_cli/projects/serverless/functions/update_function.py)
● Create(D:/antigravity_cli/projects/serverless/functions/update_function.py) (ctrl+o to expand)

  完成。以下是修改摘要:
  ──────
  ## 修改的檔案

  ### update_function.py

  修正了三個問題:

   # | 問題                                 | 修正方式
  ---|--------------------------------------|-------------------------------------------------------
   1 | 只讀  request.form ,不懂 JSON       | 用  request.is_json  判斷,JSON 時改讀
     |                                      | request.get_json()
   2 | 只認  module_name ,不認  func_name  | JSON 輸入時同時嘗試讀取  func_name  與  module_name
   3 | 成功時只回傳 HTML                    | JSON 請求時回傳  {"message": "...", "func_name":
     |                                      | "..."}
  ──────
  ## 預期執行結果
    D:\python\test>python deploy.py
    🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
    ✅ 函式更新成功!
    伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功更新'}


────────────────────────────────────────────────────────────
> update_function 是更新已有的函式, 如果要新增函式則是使用 add_function 與 save_function,
  如果要佈署新函式, deploy.py 要怎麼改? 同時請分析 add_function.py 與 save_function.py 在執行 deploy.p
  佈署方面是否有需要配合修改之處?
▸ Thought for 1s
  The user wants to understand the deployment flow for new functions. Let me first...
  先讀取三個相關檔案:

● Read(D:/antigravity_cli/projects/serverless/functions/add_function.py)
● Read(D:/antigravity_cli/projects/serverless/functions/save_function.py) (ctrl+o to expand)
  分析完畢。以下是完整報告:
  ──────
  ## 架構釐清

    add_function  → 純 UI 頁面,回傳 HTML 表單,action 指向 save_function
    save_function → 實際寫檔(新增),只接受 form POST
    update_function → 實際寫檔(覆蓋),已修改支援 JSON

  deploy.py 要新增函式,只需呼叫  save_function ,不需碰  add_function (那是給瀏覽器用的 UI)。
  ──────
  ## save_function.py 的問題(與 update_function 一樣)

   #                           | 問題                                 | 影響
  -----------------------------|--------------------------------------|-----------------------------
   1                           | 只讀  request.form ,不懂 JSON       | API Client 送來的資料讀不到
   2                           | 只認  module_name ,不認  func_name  | deploy.py 欄位名稱不符
   3                           | 成功/失敗都回傳 HTML                 |  response.json()  會爆炸
  ──────
  ## 修改計劃

  修改  save_function.py :與  update_function.py  相同邏輯,支援 JSON/form 雙輸入。

  修改  deploy.py :加入「新增 or 更新」判斷邏輯,自動選擇呼叫  save_function  或  update_function
  。

  開始修改:
● Create(D:/antigravity_cli/projects/serverless/functions/save_function.py) (ctrl+o to expand)

  現在修改  deploy.py ,加入自動判斷新增或更新:
● Create(D:/python/test/deploy.py) (ctrl+o to expand)

  完成。以下是完整摘要:
  ──────
  ## 修改了哪些檔案

   檔案                           | 修改內容
  --------------------------------|-----------------------------------------------------------------
   save_function.py               | 支援 JSON/form 雙輸入、相容  func_name / module_name 、回傳
                                  | JSON(201)或 HTML
   deploy.py                      | 新增  function_exists()  探測,自動選  save_function  或
                                  | update_function
    functions/update_function.py  | 上一輪已修改,本次不動
    add_function.py               | 不需修改,它只是瀏覽器 UI 頁面
  ──────
  ## deploy.py 新流程

    執行 deploy.py
        ↓
    function_exists():GET /function/{func_name}
        ├─ 404 → 函式不存在 → POST /function/save_function  → 回 201 ✅
        └─ 其他 → 函式已存在 → POST /function/update_function → 回 200 ✅
    ──────
  ## 預期執行結果

    # 第一次佈署(函式不存在)
    🚀 正在將 hello.py 新增至 http://127.0.0.1:5000...
    ✅ 函式新增成功!
    伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功建立'}

    # 第二次佈署(函式已存在)
    🚀 正在將 hello.py 更新至 http://127.0.0.1:5000...
    ✅ 函式更新成功!
    伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功更新'}


(2). 本地佈署測試 :

上面程序中 Claude 已經修改 serverless.py, update_function.py, 以及 save_function.py, 且修正了原先使用的佈署程式 deploy.py. 接下來要實測看看是否可在本機用佈署程式 deploy.py 將 hello.py 發布到後端平台上. 

先複習一下 serverless 平台的函式檔基本架構 :

# func_module.py
def main(request, **kwargs):
    config=kwargs.get('config', {})  # 預設為空 dict
    result='do something'
    return result

注意, main() 的參數一定要傳入 request 與關鍵字參數 **kwargs : 
  • request :
    用來接收 HTTP 請求的相關資訊 (例如前端傳進來的 Payload, Query Parameters, Headers等).
  • **kwargs 與 config :
    這是一個極具擴充性的設計, 透過 kwargs.get('config', {}) 可以在伺服器端 (Flask 主程式) 執行該函式時動態注入系統級的配置或環境變數 (例如資料庫連線字串, 加密金鑰等), 而不需要把這些敏感資訊寫死在 hello.py 裡面. 
為了展示此架構中 main() 的兩個參數用途, 先開啟 .env 檔, 添加一個環境變數 DB_TIMEOUT :

DB_TIMEOUT=30

然後按照上面的函式架構寫了一個新的 hello.py 函式檔 (其實只是加上讀取環境變數 DB_TIMEOUT 而已) :

# hello.py
def main(request, **kwargs):
    # 讀取系統注入的設定(如果有的話)
    config=kwargs.get('config', {})    
    # 這裡可以安全地取得環境變數
    db_timeout=config.get('DB_TIMEOUT', 30)
    # 先從請求字串擷取 name 參數 (?name=)
    name=request.args.get('name')
    if not name:   # 請求字串沒有 name 參數
        # 嘗試從 RESTful 子路徑 (例如 /function/hello/Tony) 中擷取 name 參數 
        subpath=request.view_args.get('subpath', '')   # 取得 Flask 傳入的 subpath 參數
        parts=subpath.strip('/').split('/')   # 拆解成串列例如 ['Tony']
        if parts:
            name=parts[0]   # 取第一段作為 name (例如 'Tony')
    # 從 request 也沒有找到
    if not name:
        name='World'    
    result=f"Hello {name}! Database timeout is set to {db_timeout}s."
    return result

然後用下列 Claude 修正過的 deploy.py 程式來佈署此新的 hello.py (注意 API_TOKEN 必須填入有效的 API Key) :

# deploy.py
import os
import requests

# 1. 設定伺服器資訊與 API Token
# 地端測試可用 http://127.0.0.1:5000,上雲端後改成你的 Render 網址
SERVER_URL="http://127.0.0.1:5000"
API_TOKEN="new-test-token"

# 2. 定義要佈署的函式名稱與本地檔案路徑
TARGET_FUNCTION_NAME="hello"
LOCAL_FILE_PATH="hello.py"

def function_exists(func_name):
    """檢查遠端伺服器上的函式是否已存在(透過 GET 探測)"""
    url=f"{SERVER_URL}/function/{func_name}"
    headers={"X-API-Key": API_TOKEN}
    try:
        response=requests.get(url, headers=headers)
        # 404 表示不存在,其他狀態(200/400/500)表示檔案存在
        return response.status_code != 404
    except requests.exceptions.RequestException:
        return False  # 連線失敗時保守假設不存在

def deploy_function():
    # 檢查本地檔案是否存在
    if not os.path.exists(LOCAL_FILE_PATH):
        print(f"❌ 找不到本地檔案: {LOCAL_FILE_PATH}")
        return
    # 讀取本地最新的程式碼內容
    with open(LOCAL_FILE_PATH, "r", encoding="utf-8") as f:
        new_code=f.read()

    headers={
        "X-API-Key": API_TOKEN,
        "Content-Type": "application/json"
    }
    payload={
        "func_name": TARGET_FUNCTION_NAME,
        "code": new_code
    }

    # 3. 自動判斷:函式已存在 → update,不存在 → save(新增)
    if function_exists(TARGET_FUNCTION_NAME):
        url=f"{SERVER_URL}/function/update_function"
        action="更新"
    else:
        url=f"{SERVER_URL}/function/save_function"
        action="新增"

    print(f"🚀 正在將 {LOCAL_FILE_PATH} {action}至 {SERVER_URL}...")
    try:
        response=requests.post(url, json=payload, headers=headers)
        # 新增成功是 201,更新成功是 200
        if response.status_code in (200, 201):
            print(f"✅ 函式{action}成功!")
            print("伺服器回應:", response.json())
        else:
            print(f"❌ {action}失敗 (狀態碼: {response.status_code})")
            try:
                print("錯誤原因:", response.json())
            except ValueError:
                print("非 JSON 回應內容:", response.text)
    except requests.exceptions.RequestException as e:
        print(f"💥 連線發生異常: {e}")

if __name__ == "__main__":
    deploy_function()

可見 Claude 修改後的 deploy.py 是透過讀取 functions 資料夾下有無該函式來判斷是要新增 (無) 還是更新 (有), 目前 functions 下有 hello.py, 所以執行 

執行結果 : 

D:\python\test>python deploy.py   
🚀 正在將 hello.py 部署至 http://127.0.0.1:5000...
✅ 函式更新成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功更新'}

接下來驗證此新佈署的 hello.py 函式功能是否正確 :






可見不同的參數傳遞方式功能都正確, 且 .env 中的環境變數 DB_TIMEOUT 也正確讀到了. 接下來測試 save_function 是否能順利佈署新函式, 先把 functions 底下的 hello.py 刪除, 然後再執行一次 deploy.py : 

D:\python\test>python deploy.py   
🚀 正在將 hello.py 新增至 http://127.0.0.1:5000...
✅ 函式新增成功!
伺服器回應: {'func_name': 'hello', 'message': '模組 hello 已成功建立'}

檢視 functions 下果然 hello.py 已被建立, 再次執行網頁測試結果相同. 

Google Antigravity 學習筆記 : 重構 serverless 函式執行平台 (三)

在前一篇測試中利用 Antigravity CLI (agy) 完成 serverless 函式執行平台的重構, 為其加上 API Key 認證機制, 所有請求必須在標頭中攜帶有效的 X-API-Key 屬性值, 否則請求將被拒絕, 使用 curl.exe 驗證新版 serverless 功能確實符合要求, 本篇則要改用 requests 套件來測試, 這是在實際應用中最常見用法. 

本系列全部文章索引參考 :



5. 使用 requests 提出請求 : 

使用 requests 套件發送請求時, 要將 API Key 放進 headers 參數中的 X-API-Key 欄位傳送給伺服器, 請求的 HTTP 方法可以使用 GET 或 POST, 分別呼叫 requests.get() 與 requests.post() 函式. 

注意, 在前一篇測試中我們已將 API Key 更新如下 :




以下測試將使用  new-test-token 這個 API Key.


(1). 使用 GET 方法 : 

GET 方法不帶 Body, 只需要單純將 API Key 放入 headers 中的 X-API-Key 欄位, 然後在呼叫 requests.get() 時傳入 headers 參數即可, 例如 : 

# serverless_api_key_get_ok.py
import requests

url="http://localhost:5000/function/hello"
headers={"X-API-Key": "new-test-token"}
response=requests.get(url, headers=headers)
if response.status_code == 200:
    print("回應:", response.text)
else:
    print(f"錯誤 {response.status_code}:", response.text)

此處因為 hello.py 傳回值是純文字, 所以用 resposne.text 而非 response.json() 取得回應值, 執行結果如下, 因為傳送了有效的 API Key, 所以伺服器回應 200 OK :

>>> %Run serverless_api_key_get_ok.py   
回應: Hello World!

如果沒有傳送有效的 API Key, 請求會被拒絕而得到 401 回應, 例如 :

# serverless_api_key_get_ng.py
import requests

url="http://localhost:5000/function/hello"
response=requests.get(url)
if response.status_code == 200:
    print("回應:", response.text)
else:
    print(f"錯誤 {response.status_code}:", response.text)

執行結果如下 :

>>> %Run serverless_api_key_get_ng.py  
錯誤 401: {
  "error": "Missing API token",
  "hint": "Provide X-API-Key header"
}


(2). 使用 POST 方法 : 

呼叫 requests.post() 時傳入有效的 API Key 就可順利取得 200 OK 回應, 例如 :

# serverless_api_key_post_ok.py
import requests

url="http://localhost:5000/function/hello"
headers={"X-API-Key": "new-test-token"}
response=requests.post(url, headers=headers)
if response.status_code == 200:
    try:
        # 先嘗試用 JSON 解析
        print("回應 (JSON):", response.json())
    except requests.exceptions.JSONDecodeError:
        # 如果不是 JSON 就印出純文字
        print("回應 (純文字):", response.text)
else:
    print(f"錯誤 {response.status_code}:", response.text)

此例使用 try catch 來判斷回應物件型態是 JSON 格式字串還是純文字, 前者呼叫 response.json() 取得回應內容, 後者則用 response.text, 執行結果如下 :

>>> %Run serverless_api_key_post_ok.py  
回應 (純文字): Hello World!

如果呼叫 requests.post() 時沒有傳入有效的 API Key 就會收到 401 錯誤, 例如 :

# serverless_api_key_post_ng.py
import requests

url="http://localhost:5000/function/hello"
response=requests.post(url)
if response.status_code == 200:
    try:
        # 先嘗試用 JSON 解析
        print("回應 (JSON):", response.json())
    except requests.exceptions.JSONDecodeError:
        # 如果不是 JSON 就印出純文字
        print("回應 (純文字):", response.text)
else:
    print(f"錯誤 {response.status_code}:", response.text)

執行結果如下 :

>> %Run serverless_api_key_post_ng.py   
錯誤 401: {
  "error": "Missing API token",
  "hint": "Provide X-API-Key header"
}

以上測試說明重構後的 serverless 平台確實已改為 API Key 版本, 任何請求必須攜帶 API Key 才可能取得 200 OK 回應, 否則請求都會被拒絕, 收到 401 錯誤. 


6. 主程式原始碼異動摘要 : 

經過上面測試, 我們確認了此重購專案已達成了預期目標, 以下做個 Code Review, 將主程式 serverless.py 的異動摘要如下 :

首先是在 init_db() 函式中添加了一個儲存 API Key 的認證資料表 api_tokens :

    # API Token 認證表
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS api_tokens (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            token      TEXT    NOT NULL UNIQUE,
            owner      TEXT    NOT NULL,
            is_active  INTEGER NOT NULL DEFAULT 1,
            created_at TEXT    NOT NULL DEFAULT (datetime('now'))
        )
    """)

其次是添加了一個 TOKEN_PROTECTED_FUNCTIONS 清單 : 

# 需要 API Token 驗證的函式列表(儲存/更新 function 及所有一般函式執行)
TOKEN_PROTECTED_FUNCTIONS=['save_function', 'update_function']

這兩個系統函式 save_function 與 update_function 用來將函式內容寫進檔案中, 屬於最高權限的敏感操作, 必須通過 API Key 驗證. 

其三是新增了一個 require_api_token() 函式來檢查請求標頭中是否有攜帶有效之 API Key, 有就傳回所呼叫的函式, 否則傳回 401 錯誤 : 

def require_api_token(f):  # 裝飾器:驗證請求 Header 中的 X-API-Key
    @wraps(f)
    def decorated(*args, **kwargs):
        token=request.headers.get('X-API-Key')
        if not token:  # Header 遺失
            return jsonify({'error': 'Missing API token', 'hint': 'Provide X-API-Key header'}), 401
        conn=sqlite3.connect(DB_PATH)
        cursor=conn.cursor()
        cursor.execute(
            'SELECT id FROM api_tokens WHERE token=? AND is_active=1',
            (token,)
        )
        row=cursor.fetchone()
        conn.close()
        if not row:  # Token 不存在或已停用
            return jsonify({'error': 'Invalid or inactive API token'}), 401
        return f(*args, **kwargs)
    return decorated

最後是在 handle_function() 函式中添加雙軌互補驗證 (Session 或 Token 擇一通過) 機制 :

    # 2. API Token 驗證:已登入管理者直接放行;否則需提供有效 Token
    #    觸發範圍:TOKEN_PROTECTED_FUNCTIONS(save/update)及所有一般函式
    if func_name in TOKEN_PROTECTED_FUNCTIONS or func_name not in PROTECTED_FUNCTIONS:
        if not check_auth():  # 管理者已登入 → 信任,跳過 Token 驗證
            token=request.headers.get('X-API-Key')
            if not token:
                return jsonify({'error': 'Missing API token', 'hint': 'Provide X-API-Key header'}), 401
            conn=sqlite3.connect(DB_PATH)
            cursor=conn.cursor()
            cursor.execute(
                'SELECT id FROM api_tokens WHERE token=? AND is_active=1',
                (token,)
            )
            row=cursor.fetchone()
            conn.close()
            if not row:
                return jsonify({'error': 'Invalid or inactive API token'}), 401

這段程式碼採用了巢狀條件設計, 提供了兩道過濾網 :
  • 第一層 (判定範圍) :只要呼叫的函式是敏感的系統功能 (save/update_function) 或任何使用者自訂函式 (不在白名單內) 就會觸發此區塊.
  • 第二層 (身份切換) :
    • 情境 A (管理員) :
      瀏覽器帶著 Cookie 請求, check_auth() 回傳 True. 此時 if not check_auth() 判定不成立, 直接跳過整個 Token 檢查與資料庫查詢, 此安排讓管理員登入後線上操作函式更新與存檔不因為沒帶 API Key 而被 401 擋掉. 
    • 情境 B (外部 API 呼叫/排程腳本) :
      本機 Python 腳本或 curl 請求沒有瀏覽器 Session, 呼叫 check_auth() 會回傳 False, 程式進入 if 內部強制執行後續的 X-API-Key 擷取與 SQLite 比對. 
if not check_auth() 是檢查使用者是否有登入, 沒有登入就要接受 API Key 查驗, 檢查 HTTP 請求的 Header 有沒有帶 X-API-Key 欄位, 沒有帶就直接拒絕請求, 回傳 401 Unauthorized 狀態碼. 如果已登入, 則程式會直接跳過整個 if not check_auth(): 內部的所有程式碼, 直接放行去執行該函式. 

注意此處使用了 SQLite 的參數化查詢 token=? 並把變數包在元組 (token,) 裡傳進去, 這樣可以徹底杜絕駭客在 Header 亂寫 SQL 語法, 利用 SQL Injection 來破解驗證. 如果資料庫回傳 None, 表示 API Key 不存在, 或存在但被停用 (is_active 不是 1), 那就回傳 401 錯誤拒絕請求; 反之就是通過了驗證, 准許呼叫此函式, 放行讓後續的業務邏輯繼續執行. 

Google Antigravity 學習筆記 : 重構 serverless 函式執行平台 (二)

在前一篇測試中, 我已將 serverless 第一版原始碼從 GitHub 上複製到本機 Antigravity CLI 的專案目錄下, 並做好前置處理, 包含將原專案打上標籤, 建立新分支, 建立隔離資料夾並移動應用 App, 最後提交 Git 紀錄. 本篇要用 agy 來進行重構, 為 serverless 平台加上 API Token 認證機制. 

本系列全部文章索引參考 :



2. 用 agy 重構專案 : 

切換到專案目錄下後輸入 agy 啟動 Antigravity CLI 介面, 這次仍然使用 Claude Sonnet 模型來完成此重構計劃. 輸入下列提示詞 :

我們正在 feature/api-token-auth 分支上,要為這個 serverless 專案加上 API Token 認證機制。請協助修改 serverless.py,完成以下任務:

1. 初始化資料庫:
   在系統啟動時(或初始化 DB 的地方),自動建立 `api_tokens` 資料表(欄位包含:id, token, owner, is_active, created_at)。

2. 實作驗證裝飾器(Decorator):
   撰寫一個 `@require_api_token` 裝飾器,從請求的 Header 中讀取 `X-API-Key`。
   - 若 Header 遺失或 Token 不存在於資料庫中(且 is_active 必須為 1),回傳 401 Unauthorized。

3. 保護 API 路由:
   將這個裝飾器套用到「儲存/更新 function」以及「執行 function」的 Flask Route 上。

請先分析原始專案的架構, 提出你的重構計劃, 讓我看過後再修改程式碼。




Claude 閱讀原專案後規劃結果如下 :

> 我們正在 feature/api-token-auth 分支上,要為這個 serverless 專案加上 API Token 認證機制。
  請協助修改 serverless.py,完成以下任務:

  1. 初始化資料庫:
     在系統啟動時(或初始化 DB 的地方),自動建立 `api_tokens` 資料表(欄位包含:id, token, owner,
  is_active, created_at)。

  2. 實作驗證裝飾器(Decorator):
     撰寫一個 `@require_api_token` 裝飾器,從請求的 Header 中讀取 `X-API-Key`。
     - 若 Header 遺失或 Token 不存在於資料庫中(且 is_active 必須為 1),回傳 401 Unauthorized。

  3. 保護 API 路由:
     將這個裝飾器套用到「儲存/更新 function」以及「執行 function」的 Flask Route 上。

  請先分析原始專案的架構, 提出你的重構計劃, 讓我看過後再修改程式碼。
▸ Thought for 2s
  Let me first explore the project structure to understand the codebase before mak...

● ListDir(D:/antigravity_cli/projects/serverless)
● Read(D:/antigravity_cli/projects/serverless/serverless.py)
● ListDir(D:/antigravity_cli/projects/serverless/functions)
● Read(D:/antigravity_cli/projects/serverless/requirements.txt)
● Read(D:/antigravity_cli/projects/serverless/functions/save_function.py)
● Read(D:/antigravity_cli/projects/serverless/functions/update_function.py) (ctrl+o to expand)

  已完整閱讀專案架構,以下是我的分析與重構計劃。
  ──────

● Create(C:/Users/tony1/.gemini/antigravity-...-a43e-cfa6fbc204e5/refactor_plan.md) (ctrl+o to expand)

  以下是重構計劃的摘要:

  ## 架構分析結論

  現有系統有兩層認證:

  1. Session Cookie — 管理者透過  /login  密碼登入,保護  PROTECTED_FUNCTIONS  清單
  2. 一般函式執行 — 完全沒有保護,任何人只要知道 URL 就能呼叫
  ──────
  ## 三個關鍵設計決策

  ### 1.  init_db()  修正

  移除  if not os.path.exists(DB_PATH)  判斷,改用  CREATE TABLE IF NOT EXISTS
  ,確保每次啟動都檢查表是否存在,不會因 DB 已建立但缺表而漏掉建立  api_tokens 。

  ### 2.  @require_api_token  裝飾器

  讀取  X-API-Key  Header → 查詢  api_tokens  表( is_active=1 )→ 不符合一律回 401 JSON。

  ### 3. 保護路由的方案

  因為  save_function 、 update_function  與一般函式執行共用同一個  handle_function()
  路由,無法直接套裝飾器。

  採用方案 A:在路由內部邏輯中呼叫驗證輔助函式,改動最小且與現有 Session 機制並存。
  ──────
  計劃文件已整理完畢,請確認後按「Proceed」或告訴我需要調整的地方,即可開始撰寫程式碼。

此計劃看來很周到, 輸入 Proceed 按 Enter 看它表演即可 :

> Proceed

▸ Thought for 1s
  The user wants to proceed with the refactoring plan. Let me implement the change...
  好的,開始實作!

● Read(D:/antigravity_cli/projects/serverless/serverless.py)
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  修改成功,來驗證最終結果:

● Read(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  內容正確。最後修正一個步驟編號的小問題(step 2 重複出現):
● Edit(D:/antigravity_cli/projects/serverless/serverless.py) (ctrl+o to expand)

  所有修改完成,以下是完整摘要:
  ──────
  ## ✅ 修改完成摘要
  檔案:serverless.py

  ### 變更一覽

   #             | 位置          | 修改內容
  ---------------|---------------|------------------------------------------------------------------
   1             | L6            | 新增  from functools import wraps
   2             | L16–37        |  init_db()  改為冪等執行,新增  api_tokens  資料表
   3             | L56–73        | 新增  require_api_token(f)  裝飾器(備用,可掛在未來獨立路由上)
   4             | L105–106      | 新增  TOKEN_PROTECTED_FUNCTIONS  常數
   5             | L167–181      |  handle_function()  步驟 2 加入 API Token 驗證邏輯
   6             | L207–208      | 移除  __main__  區塊重複的  init_db()  呼叫

  ### 認證邏輯流程(修改後)

    /function/<func_name>
        │
        ├─ func_name 在 PROTECTED_FUNCTIONS?
        │   └─ 未登入 Session → 401 (Authentication required)
        │
        ├─ func_name 在 TOKEN_PROTECTED_FUNCTIONS(save/update)
        │  OR func_name 不在 PROTECTED_FUNCTIONS(一般函式)?
        │   ├─ 無 X-API-Key Header → 401 (Missing API token)
        │   └─ Token 無效或 is_active=0 → 401 (Invalid or inactive API token)
        │
        └─ 驗證通過 → 動態載入並執行函式

  ### 快速測試方式

    # 插入測試 Token
    sqlite3 serverless.db "INSERT INTO api_tokens (token, owner) VALUES ('test-token-123',
  'developer');"

    # ✅ 正確呼叫
    curl http://localhost:5000/function/hello -H "X-API-Key: test-token-123"

    # ❌ 無 Token → 401
    curl http://localhost:5000/function/hello

不到三分鐘就完成重構了, 這若是照以前人工來改程式碼, 恐怕得花上一周或更久. 我叫 Claude 說明它改了哪些地方 :

> 解釋你修改了哪些檔案? 哪個部分(函式)?  

▸ Thought Process
  The user wants a clear explanation of which files and which functions were modif...
  這次只修改了 1 個檔案,共改動 4 個區塊:
  ──────
  ## 修改的檔案
  ### serverless.py
  ──────
  ### 1. Import 區塊(L1–8)

    + from functools import wraps

  新增  wraps ,讓裝飾器能保留被包裝函式的原始名稱與文件字串。
  ──────
  ### 2.  init_db()  函式(L16–37)
  改動前:用  if not os.path.exists(DB_PATH)  判斷,只有 DB 不存在時才建表,且只建  call_stats
  一張表。

  改動後:
  • 移除  os.path.exists  條件判斷,改為每次啟動都執行( CREATE TABLE IF NOT EXISTS
  本身就是冪等的,不會重複建表)
  • 新增  api_tokens  資料表,欄位為  id, token, owner, is_active, created_at
  ──────
  ### 3. 新增  require_api_token()  裝飾器(L56–73)

  全新函式,位於  record_call()  之後、 app = Flask(...)  之前。

  功能:從 Request Header 讀取  X-API-Key ,查詢資料庫確認 Token 存在且  is_active=1 ,否則回傳  401
  。

  │ 此裝飾器目前定義為備用,供未來掛在獨立路由上使用。
  ──────
  ### 4.  handle_function()  路由函式(L163–181)

  在原有的 Session 驗證之後,插入第二層 API Token 驗證邏輯(步驟 2):

    觸發條件(滿足其一即驗證):
      A. func_name 在 TOKEN_PROTECTED_FUNCTIONS → save_function、update_function
      B. func_name 不在 PROTECTED_FUNCTIONS → 所有一般使用者自訂函式

  兩種情況都會檢查  X-API-Key  Header,並查詢  api_tokens  表,不符合就回傳  401 。
  ──────
  ### 未修改的部分

   未動到的檔案/函式                               | 原因
  -------------------------------------------------|------------------------------------------------
    functions/  目錄下所有模組                     | 驗證邏輯集中在  serverless.py ,子模組不需變動
    check_auth()                                   | Session 登入機制保持原樣
    record_call()                                  | 呼叫統計邏輯不變
    /login 、 /logout  路由                        | 管理者登入流程不變

這個說明後續可以開啟主程式 serverless.py 來檢視. 


3. 功能測試 : 

上面 agy 已完成重構, 接下來實地測試 Claud 實作的 API Token 是否能正常運作. 在進行測試之前, 須在專案目錄下建立一個純文字檔 .env, 紀錄管理者的 token 與 key, 前者是自己容易記的密碼, 後者是用工具函式產生的一長串金鑰, 可以用 Python 內建的 secrets 模組來產生, 參考 :


在專案目錄下用記事本編輯一個 .env 檔, 輸入如下內容 (範例) :

SECRET_TOKEN=admin123
SECRET_KEY=fKFQPound41lv8FrWzU-lmFywxAT47uMwfvmBbiuLTE

其中的 SECRET_KEY 可在 Thonny 的 Python 互動環境匯入 secrets 模組後呼叫 secrets.token_urlsafe(32) 函式產生, 例如 :

>>> import secrets  
>>> secrets.token_urlsafe(32)  
'fKFQPound41lv8FrWzU-lmFywxAT47uMwfvmBbiuLTE'

由於我主要的測試環境主要是使用 Thonny 自帶的 Python, 所有 requirements.txt 中的套件在此開發環境都有安裝 (此專案主要是用到 Flask), 所以按 Thonny 的 "工具/開啟系統終端機" 開啟命令提示字元視窗, 切換到專案目錄下, 用下列指令啟動 Flask 開發伺服器 :

D:\antigravity_cli\projects\serverless>python serverless.py  
 * Serving Flask app 'serverless'
 * Debug mode: on

伺服器運行起來後, serverless.py 就會建立一個 SQlite 資料庫 serverless.db, 執行下列指令在此系統資料庫裡的 api_tokens 資料表填入測試用的權杖 test-token-123 : 

PS D:\antigravity_cli\projects\serverless> python -c "import sqlite3; conn = sqlite3.connect('serverless.db'); conn.execute('INSERT INTO api_tokens (token, owner, is_active) VALUES (?, ?, ?)', ('test-token-123', 'developer', 1)); conn.commit(); conn.close(); print('🎉 測試 Token 寫入成功!')"
🎉 測試 Token 寫入成功!

然後在瀏覽器網址列輸入 Flask 開發伺服器預設網址 http://localhost:5000/ 會看到 serverless 平台網站已順利運行, 按 "登入系統" :



輸入 .env 檔案中設定的 SECRET_TOKEN 密碼, 按 "登入" 鈕 :



登入成功後按 "函式列表" 進入管理頁面首頁 :



按 "資料表" :




可見多了一個 api_tokens 的資料表, 這就是 Claude 重構此平台時所添加, 用來儲存 API Key (token) 的資料表, 按 "檢視" :



顯示前面用 Python 指令寫入 api_tokens 資料表的測試用權杖 :




接下來用 curl.exe 測試沒帶 token 的情況, 這樣的請求會被拒絕 : 

PS D:\antigravity_cli\projects\serverless> curl.exe -i http://localhost:5000/function/hello   
HTTP/1.1 401 UNAUTHORIZED
Server: Werkzeug/2.3.7 Python/3.10.11
Date: Fri, 17 Jul 2026 11:36:12 GMT
Content-Type: application/json
Content-Length: 73
Connection: close

{
  "error": "Missing API token",
  "hint": "Provide X-API-Key header"
}

可見未攜帶 token 的請求會回應 "401 UNAUTHORIZED". 主體 (body) 內容為 JSON 訊息, error 鍵之值表示沒有收到 API token; 而 hit 鍵之值則表示必須在標頭攜帶 X-API-Key, 可在 curl.exe 指令中用 -H 參數指定此 token, 例如 : 

PS D:\antigravity_cli\projects\serverless> curl.exe -i http://localhost:5000/function/hello -H "X-API-Key: test-token-123"
HTTP/1.1 200 OK
Server: Werkzeug/2.3.7 Python/3.10.11
Date: Fri, 17 Jul 2026 15:41:03 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 12
Connection: close

Hello World!

可見若有攜帶正確的 X-API-Key 就會執行該函式, 回應 200 OK. 


4. 線上管理 API Key : 

在第一版的 serverless 函式執行平台就已實作了線上管理 serverless.db 的功能, 除了提供超連結可刪除資料表與紀錄外, 還可以執行 SQL 指令以新增, 更新, 或刪除紀錄. 

按資料表列表頁面下方的 "執行 SQL" :




輸入 SQL 指令後按 "執行" 即可, 例如要修改 owner=developer 使用中的 token 的 SQL 指令 :

UPDATE api_tokens 
SET token = 'new-test-token' WHERE owner='developer' AND is_active=1;




執行結果為成功 :




返回資料表內容檢視頁面可知 API Key (token 欄位) 已經更新了 : 




新增 API key 的 SQL 例如 : 

INSERT INTO api_tokens (token, owner, is_active) 
VALUES ('fKFQPound41lv8FrWzU-lmFywxAT47uMwfvmBbiuLTE', 'proj-a', 1);






只要以管理員身分登入, 就可以線上管理 API Key 了. 

以上測試顯示 agy 的重構確實達成了預期的目標, 讓 serverless 平台升級為需要 API Key 才能使用的網路服務, 避免伺服器被濫用或遭到攻擊.