<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Marsen L.</name>
    <email>admin@marsen.me</email>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://www.gravatar.com/avatar/c2458ad941ad5f8ca05b12c705fd4912</icon>
  <id>https://blog.marsen.me/</id>
  <link href="https://blog.marsen.me/" rel="alternate"/>
  <link href="https://blog.marsen.me/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Marsen L.</rights>
  <subtitle>waiting to load</subtitle>
  <title>Marsen's Blog</title>
  <updated>2026-06-12T11:37:08.401Z</updated>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>做形象站，Hero 要放一支滿版自動播放的背景影片，第一個冒出來的問題是「影片放哪裡」。</p><p>這個問題帶出了前端靜態資源的兩種擺法：<code>public/</code> 和 <code>src/assets/</code>。對一般人看起來只是資料夾名字不同，但對工程師來說，兩者的機制差很多。</p><blockquote><p>做形象站 Hero 背景影片的前置研究。完整的流量估算與播放器選型，見<a href="/2026/hero-background-video-hosting/">下一篇</a>。</p></blockquote><h2 id="素材放哪：public-vs-src-assets"><a href="#素材放哪：public-vs-src-assets" class="headerlink" title="素材放哪：public&#x2F; vs src&#x2F;assets"></a>素材放哪：public&#x2F; vs src&#x2F;assets</h2><p>對一般人來說看起來只是資料夾名字不同，但對這裡有蠻多的細節，我們來逐一分析。</p><p>首先要了解兩個時間點：</p><ul><li><strong>編譯期（Build Time）</strong>：網站還沒上線前，可以將一些資源優化打包減少不必要的流量，或是使用雜湊命名來避免快取問題。</li><li><strong>執行期（Runtime）</strong>：網站上線後，使用者實際使用的期間。程式在這個階段才真正執行，才能動態取得資源、回應使用者的操作。</li></ul><h3 id="src-assets-—-打包工具接管"><a href="#src-assets-—-打包工具接管" class="headerlink" title="src&#x2F;assets&#x2F; — 打包工具接管"></a>src&#x2F;assets&#x2F; — 打包工具接管</h3><p>現代主流網站打包工具（Vite &#x2F; Webpack）會在編譯期處理這些檔案：</p><ul><li><strong>雜湊檔名</strong>：輸出 <code>bg.a1b2c3d4.mp4</code>，URL 跟著內容變，瀏覽器可以放心永久 cache（<code>Cache-Control: immutable</code>）——改了檔案雜湊就換，訪客自動抓新的</li><li><strong>可能優化</strong>：圖片壓縮、小檔案 inline 成 base64 等</li><li><strong>追蹤依賴</strong>：沒有被 import 的檔案，build 產出裡就不會出現</li></ul><p><strong>限制一</strong>：<code>import</code> 是編譯期的靜態行為，URL 在 build 時就決定了，沒辦法在 runtime 動態組字串——更不可能從資料庫讀一個 URL 出來用。</p><p><strong>限制二</strong>：打包工具適合處理 JS、CSS、小圖，遇到影片這種大檔案，build 時間拉長，而且影片可壓縮或優化的效果有限，過一手只是浪費。</p><h3 id="public-—-繞過打包工具"><a href="#public-—-繞過打包工具" class="headerlink" title="public&#x2F; — 繞過打包工具"></a>public&#x2F; — 繞過打包工具</h3><p>這裡的資源不會被打包工具處理，本質跟早期 Apache &#x2F; Nginx 放一個靜態資料夾直接 serve 檔案是同一件事——收到請求，原樣回傳。快取責任落在「伺服器那層」：早期要自己設 cache header，現代部署平台（Vercel、Netlify、Cloudflare Pages）會自動推到 Edge Network 幫你卸載流量——但這是<strong>平台</strong>做的，不是框架。</p><p>URL 永遠是 <code>/你放的路徑</code>，開發時字串直接用不用 <code>import</code>：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;video src=&quot;/hero.mp4&quot; /&gt;     ← 寫死字串</span><br><span class="line">&lt;video src=&#123;dbValue&#125; /&gt;       ← 從資料庫來的字串，也走得通</span><br></pre></td></tr></table></figure><blockquote><p>Next.js 本身真正在 framework 層處理靜態資源的是 <code>next/image</code>：server side 做圖片壓縮、格式轉換、依裝置裁切，回傳正確的 cache header。<code>public/</code> 它不碰。</p></blockquote><h3 id="判斷比較"><a href="#判斷比較" class="headerlink" title="判斷比較"></a>判斷比較</h3><p>| | <code>src/assets/</code> | <code>public/</code> |<br>|　—　|　—　|　—　|<br>| 引用方式 | <code>import</code>，編譯期靜態 | 字串 URL，runtime 動態 |<br>| 快取 | 自動雜湊，可放心 <code>immutable</code> | 手動管理，或靠部署平台預設值 |<br>| 大檔案 | 不適合（影片、字型） | 適合 |<br>| 適合 | Icon、logo、小圖 | 後台可換的媒體、使用者貼的 URL |</p><blockquote><p>我們的例子是背景影片，幾乎都是「後台可換、runtime 才知道 URL」的需求 → 判斷上選擇 <code>public/</code> 較為適合。</p></blockquote><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/hero-background-video-hosting/">形象站滿版背景影片（二）：流量、CDN 與播放器選型</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li><code>src/assets/</code> 交給打包工具管，享有自動雜湊快取，但只能靜態 <code>import</code>、不適合大檔案。</li><li><code>public/</code> 繞過打包工具，直接 serve，URL 是字串、runtime 拿來用都沒問題。</li><li>影片、字型這類大媒體放 <code>public/</code>；icon、logo 等小圖用 <code>src/assets/</code>。</li><li>換 CDN 只需換 URL 字串，不用動程式碼。</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/06/12/2026/frontend-static-assets-public-vs-src-assets/</id>
    <link href="https://blog.marsen.me/2026/06/12/2026/frontend-static-assets-public-vs-src-assets/"/>
    <published>2026-06-12T11:24:23.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>做形象站，Hero 要放一支滿版自動播放的背景影片，第一個冒出來的問題是「影片放哪裡」。</p>
<p>這個問題帶出了前端靜]]>
    </summary>
    <title>[實作筆記] 形象站滿版背景影片（一）：前端靜態資源放哪裡</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="AI生成" scheme="https://blog.marsen.me/tags/AI%E7%94%9F%E6%88%90/"/>
    <content>
      <![CDATA[<p><img src="/images/ai-weekly/20260612-053413.jpg" alt="AI 週報配圖"></p><h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul><li><a href="https://openai.com/index/openai-submits-confidential-s-1">OpenAI 秘密提交 S-1 文件準備上市</a>：OpenAI 已向美國證券交易委員會 (SEC) 秘密提交 S-1 註冊草案，正式啟動 IPO 準備流程。 <a href="https://openai.com/index/openai-submits-confidential-s-1">more</a></li><li><a href="https://www.theverge.com/ai-artificial-intelligence/948280/anthropic-claude-fable-invisible-distillation-guardrail">Anthropic 為 Claude Fable 的隱形護欄道歉</a>：針對 Claude Fable 被發現存在隱性限制導致拒絕回答基礎問題，Anthropic 已出面致歉並承諾調整。 <a href="https://www.theverge.com/ai-artificial-intelligence/948280/anthropic-claude-fable-invisible-distillation-guardrail">more</a></li><li><a href="https://www.theverge.com/tech/947836/google-search-privacy-settings-images-audio">Google 將保存 Lens 照片與語音錄音用於 AI 訓練</a>：Google 更新隱私政策，確認將把 Google Lens 照片、搜尋錄音及翻譯音訊納入 AI 訓練數據集。 <a href="https://www.theverge.com/tech/947836/google-search-privacy-settings-images-audio">more</a></li><li><a href="https://www.theverge.com/tech/948890/siri-wont-be-your-ai-girlfriend">Apple 強調 Siri 不會成為 AI 女友</a>：Apple 明確表示 Siri 的定位不包含發展個人化情感關係，並與市面上的 AI 陪伴機器人劃清界線。 <a href="https://www.theverge.com/tech/948890/siri-wont-be-your-ai-girlfriend">more</a></li><li><a href="https://www.theverge.com/tech/948534/amazon-data-centers-water-use">Amazon 資料中心去年耗水 25 億加侖</a>：隨著 AI 算力需求攀升，Amazon 公開的數據顯示其資料中心冷卻系統的用水量驚人。 <a href="https://www.theverge.com/tech/948534/amazon-data-centers-water-use">more</a></li><li><a href="https://www.theverge.com/ai-artificial-intelligence/948153/deezer-ai-music-detector-spotify-apple">Deezer 推出 AI 音樂偵測工具</a>：Deezer 開發出能識別 AI 生成音樂的工具，意在打擊串流平台上的偽造內容。 <a href="https://www.theverge.com/ai-artificial-intelligence/948153/deezer-ai-music-detector-spotify-apple">more</a></li><li><a href="https://openai.com/index/openai-to-acquire-ona">OpenAI 宣佈收購 Ona</a>：OpenAI 完成對 Ona 的收購，以強化其在 AI 領域的技術布局。 <a href="https://openai.com/index/openai-to-acquire-ona">more</a></li></ul><h2 id="其他訊息"><a href="#其他訊息" class="headerlink" title="其他訊息"></a>其他訊息</h2><ul><li><a href="https://www.theverge.com/ai-artificial-intelligence/947973/fable-wont-answer-basic-biology-questions">Claude Fable 不會回答基礎生物學問題</a></li><li><a href="https://www.theverge.com/news/947831/college-speakers-booed-ai-microsoft">微軟理解學生為何對 AI 畢業演講表示不滿</a></li><li><a href="https://www.theverge.com/column/947838/washington-ai-network-honors-2026-midterms">AI 法規的未來：連結最古怪且焦慮的利益團體</a></li><li><a href="https://www.theverge.com/tech/947770/google-lyria-music-ai-lawsuit-youtube">Google 對利用 YouTube 創作者內容訓練 Lyria 音樂 AI 的隱晦態度</a></li><li><a href="https://www.theverge.com/report/947575/microsoft-claude-fable-5-restricted-internally">因數據保留疑慮，微軟限制員工使用 Claude Fable</a></li><li><a href="https://openai.com/index/preply">Preply 如何結合 AI 與人類導師實現個性化學習</a></li><li><a href="https://openai.com/index/supporting-eu-trustworthy-ai-ecosystem">支援歐洲構建可信賴的 AI 生態系統</a></li><li><a href="https://openai.com/index/bbva">BBVA 與 OpenAI 合作將 AI 置於銀行業務核心</a></li><li><a href="https://openai.com/index/using-codex-to-simulate-black-holes">天體物理學家如何利用 Codex 模擬黑洞</a></li><li><a href="https://openai.com/index/openai-on-oracle-cloud">透過 Oracle 雲端承諾存取 OpenAI 模型與 Codex</a></li><li><a href="https://openai.com/index/prc-linked-influence-operations-ai-debates">與 PRC 相關的影響力行動正在針對美國的 AI 辯論</a></li><li><a href="https://openai.com/index/lseg">從數據到決策：LSEG 如何擴展可信賴的 AI</a></li><li><a href="https://openai.com/index/nextdoor">Nextdoor 工程師如何使用 Codex 進行無限開發</a></li><li><a href="https://openai.com/index/notion">Codex 為 Notion 解鎖了哪些功能</a></li><li><a href="https://openai.com/index/industrial-policy-for-the-intelligence-age">智慧時代的產業政策</a></li><li><a href="https://openai.com/index/built-to-benefit-everyone-our-plan">旨在造福所有人：我們的計畫</a></li><li><a href="https://openai.com/index/economic-research-exchange">介紹 OpenAI 經濟研究交流</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/06/11/2026/ai-weekly-20260612/</id>
    <link href="https://blog.marsen.me/2026/06/11/2026/ai-weekly-20260612/"/>
    <published>2026-06-11T21:33:57.000Z</published>
    <summary>
      <![CDATA[<p><img src="/images/ai-weekly/20260612-053413.jpg" alt="AI 週報配圖"></p>
<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要]]>
    </summary>
    <title>[AI生成] 20260612 科技周報</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="AI生成" scheme="https://blog.marsen.me/tags/AI%E7%94%9F%E6%88%90/"/>
    <content>
      <![CDATA[<p><img src="/images/ai-weekly/20260610-080702.jpg" alt="AI 週報配圖"></p><h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul><li><a href="https://openai.com/index/openai-submits-confidential-s-1">秘密向 SEC 提交 S-1 註冊聲明草案</a>：OpenAI 宣布已向美國證券交易委員會（SEC）秘密提交了 S-1 格式的招股說明書草案，準備進行首次公開募股（IPO）。這標誌著該公司在商業化與資本市場化道路上的重大里程碑。<a href="https://openai.com/index/openai-submits-confidential-s-1">more</a></li><li><a href="https://openai.com/index/industrial-policy-for-the-intelligence-age">智能時代的產業政策</a>：OpenAI 提出了針對智能時代的產業政策藍圖，強調需要建立強大的 AI 基礎設施，包括能源、晶片及數據中心。此政策旨在確保國家競爭力並促進 AI 技術的民主化與安全發展。<a href="https://openai.com/index/industrial-policy-for-the-intelligence-age">more</a></li><li><a href="https://openai.com/index/chatgpt-memory-dreaming">築夢：為更具幫忙能力的 ChatGPT 提供更好的記憶力</a>：OpenAI 推出名為「Dreaming」的新技術，旨在升級 ChatGPT 的記憶機制。透過在後台進行「夢境」般的離線整合，ChatGPT 能更有效率地整理與回顧過往的對話上下文，提供更具連貫性且個人化的互動體驗。<a href="https://openai.com/index/chatgpt-memory-dreaming">more</a></li><li><a href="https://openai.com/index/built-to-benefit-everyone-our-plan">旨在造福大眾：我們的計劃</a>：OpenAI 發表了其未來的發展計劃，重申確保通用人工智能（AGI）造福全人類的使命。該計劃詳細說明了治理結構的優化以及如何將 AI 技術帶來的經濟價值公平地分配給社會各界。<a href="https://openai.com/index/built-to-benefit-everyone-our-plan">more</a></li><li><a href="https://openai.com/index/introducing-new-capabilities-to-gpt-rosalind">介紹 GPT-Rosalind 的全新功能</a>：OpenAI 宣布為 GPT-Rosalind 引入全新功能，提升其在科學研究與複雜邏輯推理方面的表現。這次更新將使該模型能更有效地協助生物學及其他前沿科學領域的研究人員進行探索。<a href="https://openai.com/index/introducing-new-capabilities-to-gpt-rosalind">more</a></li><li><a href="https://openai.com/index/economic-research-exchange">推出 OpenAI 經濟研究交流平台</a>：OpenAI 成立了「經濟研究交流平台（Economic Research Exchange）」，旨在與全球學術界和政策制定者合作，共同研究 AI 對勞動力市場、生產力以及整體經濟結構的深遠影響。<a href="https://openai.com/index/economic-research-exchange">more</a></li><li><a href="https://openai.com/index/biodefense-in-the-intelligence-age">智能時代的生物防禦</a>：本文探討了在 AI 快速發展的時代下，如何利用先進模型來強化全球生物防禦系統。OpenAI 強調了制定安全防護措施的重要性，以防止技術被濫用，同時利用 AI 預防與應對潛在的生物安全威脅。<a href="https://openai.com/index/biodefense-in-the-intelligence-age">more</a></li></ul><h2 id="其他訊息"><a href="#其他訊息" class="headerlink" title="其他訊息"></a>其他訊息</h2><ul><li><a href="https://openai.com/index/lseg">從數據到決策：LSEG 如何擴展可信賴的 AI</a></li><li><a href="https://openai.com/index/nextdoor">Nextdoor 的工程師如何利用 Codex 進行無限創意的構建</a></li><li><a href="https://openai.com/index/notion">Codex 為 Notion 釋放了什麼潛能</a></li><li><a href="https://openai.com/index/endava-frontiers">Endava 如何圍繞 AI 代理重新設計軟體交付</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/06/10/2026/ai-weekly-20260610/</id>
    <link href="https://blog.marsen.me/2026/06/10/2026/ai-weekly-20260610/"/>
    <published>2026-06-10T00:05:40.000Z</published>
    <summary>
      <![CDATA[<p><img src="/images/ai-weekly/20260610-080702.jpg" alt="AI 週報配圖"></p>
<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要]]>
    </summary>
    <title>[AI生成] 20260610 科技周報</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>Hero 要放一支滿版自動播放的背景影片。</p><blockquote><p>Hero 區塊是網頁最頂部的全版區塊，通常含標題、說明文字與背景，是訪客進站看到的第一眼。</p></blockquote><p>素材要放哪，已在<a href="/2026/frontend-static-assets-public-vs-src-assets/">上一篇</a>整理好了，結論是放 <code>public/</code>，URL 字串直接用。</p><p>這篇專注在另外兩個問題：</p><ol><li><strong>流量控管</strong>：滿版自動播放背景影片是首頁最吃流量的東西，怎麼估、怎麼省？</li><li><strong>UI 體驗</strong>：背景影片要無控制列、自動播放、靜音、循環——原生 <code>&lt;video&gt;</code> 還是嵌入 YouTube／Vimeo？</li></ol><h2 id="自存影片：會吃多少流量？"><a href="#自存影片：會吃多少流量？" class="headerlink" title="自存影片：會吃多少流量？"></a>自存影片：會吃多少流量？</h2><p>會，而且<strong>滿版自動播放的背景影片，是整頁最吃流量的東西</strong>。</p><p>粗估一下：一支壓到 SD 的背景影片約 3.5MB，每個訪客每次載入首頁就抓一次（瀏覽器會快取，回訪者不重抓）。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">3.5MB × 10,000 次造訪/月 ≈ 35GB/月</span><br></pre></td></tr></table></figure><p>對小型品牌站通常還在額度內，但要注意兩件事：</p><ol><li>行動裝置上，這 3.5MB 是吃<strong>訪客自己</strong>的行動數據，UX 不友善。</li><li>影片越大支越可怕，HD 1080p 動輒 12MB 起跳。</li></ol><h3 id="重點觀念：走-CDN-≠-不算流量"><a href="#重點觀念：走-CDN-≠-不算流量" class="headerlink" title="重點觀念：走 CDN ≠ 不算流量"></a>重點觀念：走 CDN ≠ 不算流量</h3><p>很多人以為「丟到平台、平台有 CDN」就沒事了。</p><p>以主流部署平台來說，<code>public/</code> 的靜態檔確實會由它的 Edge Network（CDN）快取分送——<strong>但傳輸量仍然計入你的方案額度</strong>。</p><p>CDN 解決的是「分送速度與快取」，不是「免費流量」。要真正卸載流量，得換 host。</p><h2 id="要省流量：CDN-影音-host-比較"><a href="#要省流量：CDN-影音-host-比較" class="headerlink" title="要省流量：CDN &#x2F; 影音 host 比較"></a>要省流量：CDN &#x2F; 影音 host 比較</h2><p>影片放在 <code>public/</code> 的話，流量算在部署平台的額度裡。</p><p>以我們用的平台　Vercel 免費方案只有 100GB&#x2F;月，一支背景影片流量大的時候很快就吃光。</p><p>解法是把影片搬到外部 host，讓別人的伺服器去扛流量。</p><p>好消息是，只要程式碼只用 URL 字串引用影片，換 host 就是換那個字串，<strong>不用改程式碼</strong>：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 原本放 public/</span><br><span class="line">&lt;video src=&quot;/hero.mp4&quot; /&gt;</span><br><span class="line"></span><br><span class="line">// 搬到 R2 後，只改這一行</span><br><span class="line">&lt;video src=&quot;https://pub-xxx.r2.dev/hero.mp4&quot; /&gt;</span><br></pre></td></tr></table></figure><p>兩條路線：</p><h3 id="路線一：自己存，外部-CDN-來送（Cloudflare-R2"><a href="#路線一：自己存，外部-CDN-來送（Cloudflare-R2" class="headerlink" title="路線一：自己存，外部 CDN 來送（Cloudflare R2"></a>路線一：自己存，外部 CDN 來送（Cloudflare R2</h3><p>把影片傳到 R2，egress（流出流量）免費，只付儲存費（$0.015&#x2F;GB&#x2F;月）。一支 5MB 影片幾乎是 $0，部署平台那邊完全不計這筆流量。</p><h3 id="路線二：讓別人的平台吸收（Vimeo-Youtube）"><a href="#路線二：讓別人的平台吸收（Vimeo-Youtube）" class="headerlink" title="路線二：讓別人的平台吸收（Vimeo&#x2F;Youtube）"></a>路線二：讓別人的平台吸收（Vimeo&#x2F;Youtube）</h3><p>影片上傳到 Vimeo，你的伺服器完全不出流量，由 Vimeo 負責送給訪客。代價是多了第三方依賴，免費方案有上傳量限制。</p><table><thead><tr><th>方案</th><th>流量誰扛</th><th>備註</th></tr></thead><tbody><tr><td>Cloudflare R2</td><td>R2（egress 免費）</td><td>自己管檔案，換 URL 即可</td></tr><tr><td>Cloudflare Stream &#x2F; Mux</td><td>平台</td><td>專業影音串流，自動轉檔、自適應位元率</td></tr><tr><td>Cloudinary</td><td>平台</td><td>媒體優化 + CDN</td></tr><tr><td>Vimeo</td><td>Vimeo</td><td>免費方案有上傳限制，背景模式乾淨</td></tr><tr><td>YouTube 嵌入</td><td>YouTube（完全免費）</td><td>有播放器外觀問題（下節詳述）</td></tr></tbody></table><p>形象站的滿版背景，<strong>通常不會用 YouTube</strong>，原因看下面。</p><h2 id="YouTube-當「無播放器」背景？hack-與代價"><a href="#YouTube-當「無播放器」背景？hack-與代價" class="headerlink" title="YouTube 當「無播放器」背景？hack 與代價"></a>YouTube 當「無播放器」背景？hack 與代價</h2><p>YouTube 這麼主流，能不能拿來當乾淨背景？</p><p><strong>沒有官方乾淨方案</strong>，主流做法都是 hack，而且 YouTube 近年反而把「藏品牌」收得更緊。</p><p>做法是 IFrame Player API + 參數 + CSS：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">autoplay=1&amp;mute=1&amp;loop=1&amp;playlist=VIDEO_ID&amp;controls=0&amp;playsinline=1&amp;rel=0&amp;disablekb=1&amp;iv_load_policy=3</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 把 iframe 放大超出視窗、置中裁切，藏掉黑邊與殘留 UI */</span></span><br><span class="line"><span class="selector-class">.bg-wrap</span> &#123; <span class="attribute">position</span>: absolute; inset: <span class="number">0</span>; <span class="attribute">overflow</span>: hidden; &#125;</span><br><span class="line"><span class="selector-class">.bg-wrap</span> <span class="selector-tag">iframe</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute; <span class="attribute">top</span>: <span class="number">50%</span>; <span class="attribute">left</span>: <span class="number">50%</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">150vw</span>; <span class="attribute">height</span>: <span class="number">150vh</span>;        <span class="comment">/* 刻意超出 */</span></span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">translate</span>(-<span class="number">50%</span>, -<span class="number">50%</span>);</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;                <span class="comment">/* 防止點到跳出控制列 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代價很實際：</p><ul><li><code>modestbranding</code> 已被 YouTube 淡化／移除，載入、暫停、結束仍可能<strong>閃一下 logo／標題</strong>。</li><li>單片循環要用 <code>loop=1&amp;playlist=ID</code> 的 trick，<strong>接點會黑屏跳一下</strong>（要用 API <code>seekTo(0)</code> 才順）。</li><li>16:9 硬裁成滿版 → <strong>邊緣被切掉</strong>。</li><li>比原生 <code>&lt;video&gt;</code> <strong>重很多</strong>（要載播放器 JS、追蹤 cookie，除非用 <code>youtube-nocookie</code>）。</li><li><strong>手機（尤其 iOS）自動播放不穩</strong>。</li><li>「完全藏品牌」本身遊走在 ToS 邊緣。</li></ul><h2 id="真正乾淨的主流解"><a href="#真正乾淨的主流解" class="headerlink" title="真正乾淨的主流解"></a>真正乾淨的主流解</h2><h3 id="1-Vimeo-背景模式"><a href="#1-Vimeo-背景模式" class="headerlink" title="1. Vimeo 背景模式"></a>1. Vimeo 背景模式</h3><p>Vimeo 的嵌入支援 <code>background=1</code> 參數，這是<strong>官方為背景影片設計</strong>的模式：自動播放、循環、靜音、<strong>全程無控制列、無品牌</strong>。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://player.vimeo.com/video/VIDEO_ID?background=1&amp;muted=1&amp;autoplay=1&amp;loop=1</span><br></pre></td></tr></table></figure><p>要 iframe 又要乾淨，這才是該選的，比 YouTube hack 乾淨太多。</p><h3 id="2-影音-CDN-的-mp4-原生-video"><a href="#2-影音-CDN-的-mp4-原生-video" class="headerlink" title="2. 影音 CDN 的 mp4 + 原生 video"></a>2. 影音 CDN 的 mp4 + 原生 video</h3><p>把影片放 Stream &#x2F; Mux &#x2F; R2，拿到真正的 mp4／HLS URL，用原生 <code>&lt;video&gt;</code> 播。</p><p>最乾淨、最可控，也最好做手機降級（poster、<code>preload</code>、小螢幕只給圖）。</p><h2 id="架構建議：媒體以-URL-為主"><a href="#架構建議：媒體以-URL-為主" class="headerlink" title="架構建議：媒體以 URL 為主"></a>架構建議：媒體以 URL 為主</h2><p>把這些湊起來，得到一個簡單原則：<strong>渲染層不要綁死 host。</strong></p><ul><li>媒體只存一個 URL 字串（哪來的都行：自家 <code>public/</code>、R2、Mux、Vimeo iframe）。</li><li>範例／預設素材放 <code>public/</code>，跟使用者貼的網址走同一條路徑。</li><li>正式上線要換成 CDN？<strong>貼上新 URL 即可，不用改程式碼。</strong></li><li>要支援 iframe 型（Vimeo／YouTube）就多開一種「媒體類型」，渲染分流到 iframe，原生影片仍走 <code>&lt;video&gt;</code>。</li></ul><p>這樣早期可以自存 demo、之後無痛換成專業影音 CDN，決策成本最低。</p><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/frontend-static-assets-public-vs-src-assets/">形象站滿版背景影片（一）：前端靜態資源放哪裡</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>滿版自動播放背景影片最吃流量；CDN 加速 ≠ 免費流量，傳輸仍算額度。</li><li>省流量靠換 host：R2（egress 免費）&#x2F; Stream &#x2F; Mux &#x2F; Cloudinary。</li><li>YouTube 無播放器背景只能 hack，缺點一堆；要 iframe 乾淨解請用 <strong>Vimeo <code>background=1</code></strong>。</li><li>最乾淨：影音 CDN 的 mp4 + 原生 <code>&lt;video&gt;</code>。</li><li>架構上讓媒體「只認 URL」，換 host 就是換字串，不用動程式碼。</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/06/07/2026/hero-background-video-hosting/</id>
    <link href="https://blog.marsen.me/2026/06/07/2026/hero-background-video-hosting/"/>
    <published>2026-06-07T16:27:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>Hero 要放一支滿版自動播放的背景影片。</p>
<blockquote>
<p>Hero 區塊是網頁最頂部的全版區塊，通]]>
    </summary>
    <title>[實作筆記] 形象站滿版背景影片（二）：流量、CDN 與播放器選型</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="AI生成" scheme="https://blog.marsen.me/tags/AI%E7%94%9F%E6%88%90/"/>
    <content>
      <![CDATA[<p><img src="/images/ai-weekly/20260605-045626.jpg" alt="AI 週報配圖"></p><h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul><li><a href="https://openai.com/index/openai-frontier-models-and-codex-are-now-available-on-aws">OpenAI 前沿模型與 Codex 現已登陸 AWS</a>：OpenAI 宣布其前沿模型與 Codex 系列正式整合至 AWS 平台，協助企業開發者更高效地構建 AI 應用。 <a href="https://openai.com/index/openai-frontier-models-and-codex-are-now-available-on-AWS">more</a></li><li><a href="https://openai.com/index/chatgpt-memory-dreaming">ChatGPT 的記憶優化計畫：Dreaming</a>：OpenAI 推出「Dreaming」機制，旨在強化 ChatGPT 的記憶功能，使其能更精準且具備連貫性地提供使用者所需的協助。 <a href="https://openai.com/index/chatgpt-memory-dreaming">more</a></li><li><a href="https://openai.com/index/frontier-safety-blueprint">為前沿 AI 建立民主化治理藍圖</a>：OpenAI 提出一套關於前沿 AI 治理的藍圖，強調在發展尖端技術的同時，必須確保其安全性、透明度與全球性的民主參與。 <a href="https://openai.com/index/frontier-safety-blueprint">more</a></li><li><a href="https://openai.com/index/stargate-michigan-data-center">在密西根州構建智慧時代的基礎設施</a>：OpenAI 宣布將在密西根州建立大型數據中心項目 Stargate，為 AI 運算需求提供強大的硬體支持。 <a href="https://openai.com/index/stargate-michigan-data-center">more</a></li><li><a href="https://openai.com/index/endava-frontiers">Endava 如何圍繞 AI Agents 重塑軟體交付</a>：Endava 透過引入 AI Agents 技術，徹底改變了傳統軟體開發與交付的流程，大幅提升了開發效率。 <a href="https://openai.com/index/endava-frontiers">more</a></li><li><a href="https://openai.com/index/boston-childrens-hospital">波士頓兒童醫院利用 AI 開啟新的診斷路徑</a>：波士頓兒童醫院正應用 OpenAI 的技術輔助醫師進行複雜病例的診斷，為醫療領域帶來了新的突破。 <a href="https://openai.com/index/boston-childrens-hospital">more</a></li><li><a href="https://openai.com/index/travelers">Travelers 保險公司在全國範圍部署 AI 理賠系統</a>：保險巨頭 Travelers 與 OpenAI 合作，全面部署 AI 驅動的理賠處理流程，顯著縮短了客戶等待時間。 <a href="https://openai.com/index/travelers">more</a></li></ul><h2 id="其他訊息"><a href="#其他訊息" class="headerlink" title="其他訊息"></a>其他訊息</h2><ul><li><a href="https://openai.com/index/biodefense-in-the-intelligence-age">智慧時代的生物防禦</a></li><li><a href="https://openai.com/index/introducing-new-capabilities-to-gpt-rosalind">為 GPT-Rosalind 引入全新能力</a></li><li><a href="https://openai.com/index/wasmer">Wasmer 如何利用 Codex 建構 Edge 端的 Node.js 運行時</a></li><li><a href="https://openai.com/index/public-policy-agenda">OpenAI 公共政策議程</a></li><li><a href="https://openai.com/index/codex-for-every-role-tool-workflow">Codex 適用於所有職位、工具與工作流</a></li><li><a href="https://openai.com/index/advancing-youth-safety-and-opportunity-through-global-leadership">透過全球領導力推動青年安全與機遇</a></li><li><a href="https://openai.com/index/codex-for-knowledge-work">Codex 正成為全民生產力工具</a></li><li><a href="https://openai.com/index/our-views-on-ai-policy-and-political-advocacy">我們對 AI 政策與政治倡議的觀點</a></li><li><a href="https://openai.com/index/braintrust">Braintrust 如何利用 Codex 將客戶需求轉化為程式碼</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/06/04/2026/ai-weekly-20260605/</id>
    <link href="https://blog.marsen.me/2026/06/04/2026/ai-weekly-20260605/"/>
    <published>2026-06-04T20:56:17.000Z</published>
    <summary>
      <![CDATA[<p><img src="/images/ai-weekly/20260605-045626.jpg" alt="AI 週報配圖"></p>
<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要]]>
    </summary>
    <title>[AI生成] 20260605 科技周報</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>這幾天整理 Cloudflare 的 DNS，看到幾筆我沒有記憶也不理解的記錄：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">send.marsen.me   MX   feedback-smtp.ap-northeast-1.amazonses.com</span><br></pre></td></tr></table></figure><p>Amazon SES？我什麼時候設過？還在用嗎？敢不敢刪？</p><p>不確定就不要亂刪 DNS——刪錯一筆，輕則信進垃圾桶，重則收不到信。所以乾脆把整個 <code>marsen.me</code> 上的 email 服務盤點一次，搞清楚每一筆 DNS 到底在幹嘛。</p><h2 id="第一步，列出-DNS-Record"><a href="#第一步，列出-DNS-Record" class="headerlink" title="第一步，列出 DNS Record"></a>第一步，列出 DNS Record</h2><p>把 DNS 裡跟 email 有關的記錄抓出來，對應到服務：</p><p>| 服務 | 相關 DNS 記錄 |<br>|　——　|————–|<br>| Cloudflare Email Routing | <code>marsen.me</code> MX（route1-3.mx.cloudflare.net） |<br>| Brevo（原 Sendinblue） | <code>brevo1/2._domainkey</code> CNAME |<br>| Resend | <code>resend._domainkey</code> TXT |<br>| Amazon SES | <code>send.marsen.me</code> MX + SPF |</p><p>四套服務、一堆 DKIM&#x2F;SPF&#x2F;MX，光看 DNS 根本分不出哪些還在使用，用途為何？</p><h2 id="第二步，理解-DNS-Record"><a href="#第二步，理解-DNS-Record" class="headerlink" title="第二步，理解 DNS Record"></a>第二步，理解 DNS Record</h2><p>可以參考文章最末的參考資料，這裡簡單說明 MX 與 SPF，以後有機會再進一步說明</p><p><strong>MX</strong>: 告訴別人「寄到 @你的網域 的信，送去哪台伺服器，  </p><p>你家的<strong>收件信箱地址</strong>，別人寄東西來才送得到。</p><p><strong>SPF &#x2F; DKIM</strong> &#x3D; 你寄信時的<strong>防偽印章和授權書</strong>。</p><p>證明「這封信真的是你（或你授權的服務）寄的」，防止被當垃圾或冒名</p><p>所以一個純發信的服務（像 Brevo、Resend），只需要 DKIM 印章，<strong>根本不需要 MX</strong>。這個觀念是後面判讀的基礎。</p><blockquote><p>補充：SPF 和 DKIM 的內容本體都是 TXT，但 DKIM 有兩種發布方式——<br>一種像 Resend 是把公鑰直接寫成 TXT 給你貼；<br>另一種像 Brevo 則是給你一筆 <strong>CNAME</strong>，把 <code>brevoX._domainkey</code> 委派指向它自家的 DNS，公鑰放在它那邊，方便它自己輪替金鑰。<br>所以上面表格裡 Brevo 是 CNAME、Resend 是 TXT，但做的是同一件事。</p></blockquote><h2 id="寄收信測試"><a href="#寄收信測試" class="headerlink" title="寄收信測試"></a>寄收信測試</h2><p>我們可以查看<strong>郵件 header</strong>——它記錄了一封信實際走過的每一站。</p><p>方法很簡單：寄一封測試信給自己，打開「顯示原始郵件」看 header。</p><p>我用 <code>admin@marsen.me</code> 寄一封信給自已</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Message-Id: &lt;...@smtp-relay.sendinblue.com&gt;</span><br><span class="line">Received: from hb.d.sender-sib.com (...)</span><br><span class="line">DKIM-Signature: ... d=marsen.me s=brevo2 ...   → PASS</span><br><span class="line">Feedback-ID: ...:Sendinblue</span><br><span class="line">Return-Path: &lt;...=hb.d.sender-sib.com=bounces-...@marsen.me&gt;</span><br></pre></td></tr></table></figure><p><code>sendinblue</code>、<code>sender-sib</code>（SendInBlue 的縮寫）、<code>s=brevo2</code> 的 DKIM 簽章——這封信百分之百走 Brevo 發出。</p><p><code>s=brevo2</code> 還正好對應到我 DNS 裡的 <code>brevo2._domainkey</code>。<strong>Brevo 確認在用。</strong></p><h3 id="同一封信，還證明了收信端"><a href="#同一封信，還證明了收信端" class="headerlink" title="同一封信，還證明了收信端"></a>同一封信，還證明了收信端</h3><p>更妙的是，這封我寄給 <code>tester@marsen.me</code> 的信，被轉發回我 Gmail，header 把收信路徑也記下來了：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Received: by cloudflare-email.net       → Cloudflare Email Routing 收到</span><br><span class="line">X-Forwarded-To: me@gmail.com</span><br><span class="line">X-Forwarded-For: tester@marsen.me me@gmail.com</span><br><span class="line">Delivered-To: me@gmail.com</span><br></pre></td></tr></table></figure><p>一封信同時驗證了兩端：<strong>Brevo 發信</strong> → <strong>Cloudflare Email Routing 收信並轉發</strong> → 進 Gmail。</p><h2 id="那「收信」到底是怎麼運作的？"><a href="#那「收信」到底是怎麼運作的？" class="headerlink" title="那「收信」到底是怎麼運作的？"></a>那「收信」到底是怎麼運作的？</h2><p>順著 header 就看懂了，我的收信是 <strong>Cloudflare Email Routing</strong>：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">別人寄到 xxx@marsen.me</span><br><span class="line">  → marsen.me 的 MX 指向 Cloudflare（route1-3.mx.cloudflare.net）</span><br><span class="line">  → Cloudflare Email Routing 收到</span><br><span class="line">  → 依轉發規則（Routes）轉寄</span><br><span class="line">  → 我的真實 Gmail</span><br></pre></td></tr></table></figure><p>重點：Cloudflare Email Routing 是<strong>轉發</strong>，不是信箱。</p><p>它不存信，收到就立刻轉寄到你真正的 Gmail。所以 <code>@marsen.me</code> 沒有獨立的信箱空間，所有信最終都進同一個 Gmail。</p><p>整套自訂網域信箱長這樣：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">            ┌─ 收信 → Cloudflare Email Routing（MX）→ 轉發 ┐</span><br><span class="line">@marsen.me ─┤                                              ├→ Gmail</span><br><span class="line">            └─ 寄信 ← Brevo SMTP relay ← Gmail「以 admin@ 寄」┘</span><br></pre></td></tr></table></figure><p>用免費服務，把 Gmail 變成 <code>@marsen.me</code> 的網域信箱。</p><p>這就是我免費達成私有 domain 信件寄收的作法，但是要注意 Brevo 有每日 300 封信的使用上限</p><h2 id="盤點結果"><a href="#盤點結果" class="headerlink" title="盤點結果"></a>盤點結果</h2><p>三重交叉查證（程式碼 grep、n8n 備份、郵件 header）之後：</p><p>| 服務 | 狀態 | 證據 |<br>|　——|——|——|<br>| Cloudflare Email Routing | ✅ 在用 | header 轉發路徑 |<br>| Brevo | ✅ 在用 | header 的 sendinblue &#x2F; brevo2 簽章 |<br>| Resend | ✅ 在用 | 電商專案發訂單信，程式碼有引用 |<br>| <strong>Amazon SES</strong> | ❌ <strong>殘留</strong> | 任何地方都找不到使用，連 SES 驗證記錄都沒有 |</p><p>SES 是當初設定網域信箱時評估過的備選方案（按量計費、便宜但設定複雜），最後選了 Brevo，</p><p>但 AWS 用的　<code>send.marsen.me</code> 的 MAIL FROM 記錄留下來沒清。</p><h2 id="為什麼在用的-Brevo-沒有-MX，沒用的-SES-反而有？"><a href="#為什麼在用的-Brevo-沒有-MX，沒用的-SES-反而有？" class="headerlink" title="為什麼在用的 Brevo 沒有 MX，沒用的 SES 反而有？"></a>為什麼在用的 Brevo 沒有 MX，沒用的 SES 反而有？</h2><p>這題剛好回扣前面「收信才用 MX」的觀念，但有個例外：</p><ul><li><strong>Brevo</strong>：純發信，靠 DKIM + SPF 驗證授權，<strong>不需要 MX</strong>。所以它在用，卻沒有 MX。</li><li><strong>SES</strong>：那個 <code>send.marsen.me</code> 的 MX <strong>不是收一般信</strong>，而是 SES 的「custom MAIL FROM」機制要求的，用來接收<strong>退信&#x2F;投訴回報</strong>（bounce&#x2F;complaint）。</li></ul><p>諷刺的是：沒在用的 SES 因為特殊機制留了 MX，在用的 Brevo 因為純發信反而沒有 MX。</p><p>光看「有沒有 MX」完全判斷不出哪個在用。</p><h2 id="安全清理"><a href="#安全清理" class="headerlink" title="安全清理"></a>安全清理</h2><p>確認 SES 是殘留後，要刪的只有兩筆（name 都是 <code>send.marsen.me</code>）。</p><p><strong>刪之前先把完整內容記下來</strong>——理論上它一定沒用了，但留個底，萬一哪天要恢復可以照著重建：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ── 被刪除的 SES 殘留記錄（刪除日：2026-06-04，留供日後恢復）──</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第一筆：SES custom MAIL FROM 的 bounce 接收</span></span><br><span class="line"><span class="attr">Type</span>     = MX</span><br><span class="line"><span class="attr">Name</span>     = send.marsen.me</span><br><span class="line"><span class="attr">Content</span>  = feedback-smtp.ap-northeast-<span class="number">1</span>.amazonses.com</span><br><span class="line"><span class="attr">Priority</span> = <span class="number">10</span></span><br><span class="line"><span class="attr">Proxy</span>    = DNS <span class="literal">on</span>ly</span><br><span class="line"><span class="attr">TTL</span>      = <span class="number">1</span> hour</span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二筆：SES 的 SPF</span></span><br><span class="line"><span class="attr">Type</span>     = TXT</span><br><span class="line"><span class="attr">Name</span>     = send.marsen.me</span><br><span class="line"><span class="attr">Content</span>  = <span class="string">&quot;v=spf1 include:amazonses.com ~all&quot;</span></span><br><span class="line"><span class="attr">Proxy</span>    = DNS <span class="literal">on</span>ly</span><br><span class="line"><span class="attr">TTL</span>      = <span class="number">1</span> hour</span><br></pre></td></tr></table></figure><blockquote><p>註：若真要復原 Amazon SES 寄信，光補這兩筆 DNS 不夠，還得回 AWS Console（<code>ap-northeast-1</code> 東京）重新驗證 identity 與 custom MAIL FROM。這兩筆只是當時設定留下的 DNS 半成品。</p></blockquote><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><p>盤點 email DNS 殘留，兩個心法：</p><ol><li><strong>分清收信和發信</strong>——MX 是收信，SPF&#x2F;DKIM 是發信驗證。看到一筆記錄先問它管的是收還是發。</li><li><strong>用郵件 header 當偵探</strong>——dashboard 會騙人，header 不會。寄封測試信，原始 header 裡寫得清清楚楚這封走了哪些服務。記得用<strong>網域地址</strong>寄，別用個人信箱，不然測了個寂寞。</li></ol><p>殘留通常來自「評估過但沒採用的方案」——設定設了一半，淘汰後忘了清。盤點一次，把死記錄清掉，DNS 才乾淨。</p><h2 id="補充：DNS-記錄類型速查"><a href="#補充：DNS-記錄類型速查" class="headerlink" title="補充：DNS 記錄類型速查"></a>補充：DNS 記錄類型速查</h2><table><thead><tr><th>記錄</th><th>說明</th></tr></thead><tbody><tr><td><strong>MX</strong></td><td>收信路由。告訴外部郵件伺服器「寄到這個網域的信，應該送到哪台 SMTP 伺服器」，數字優先度越小越優先，可設多筆做備援</td></tr><tr><td><strong>TXT</strong></td><td>純文字記錄，DNS 裡的萬用槽。SPF、DKIM 公鑰、網域所有權驗證都塞這裡</td></tr><tr><td><strong>CNAME</strong></td><td>別名記錄。把一個 subdomain 指向另一個 hostname，由對方那端負責最終解析。根網域（<code>marsen.me</code> 本身）不能用</td></tr><tr><td><strong>SPF</strong></td><td>發信授權白名單，格式寫在 TXT 裡（<code>v=spf1 ...</code>）。聲明哪些 IP 或服務有資格以這個網域寄信，收件方用來驗發件 IP</td></tr><tr><td><strong>DKIM</strong></td><td>郵件防偽簽章。發信服務用私鑰簽章，公鑰掛在 DNS（TXT 或 CNAME 委派），收件方取公鑰驗簽，確認內容未被竄改</td></tr><tr><td><strong>A</strong></td><td>最基本的記錄。把網域直接對應到一個 IPv4 位址</td></tr><tr><td><strong>AAAA</strong></td><td>同 A，對應的是 IPv6 位址</td></tr><tr><td><strong>NS</strong></td><td>管轄聲明。指定這個網域的 DNS 問題，由哪幾台 Name Server 負責回答</td></tr></tbody></table><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2025/08/27/2025/brevo_smtp/">怎麼建立一個網站？(四) - 自訂網域 EMail 收寄信（使用 Cloudflare 與 Brevo）</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/06/03/2026/email-dns-audit-cleanup/</id>
    <link href="https://blog.marsen.me/2026/06/03/2026/email-dns-audit-cleanup/"/>
    <published>2026-06-03T16:11:18.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>這幾天整理 Cloudflare 的 DNS，看到幾筆我沒有記憶也不理解的記錄：</p>
<figure class="hi]]>
    </summary>
    <title>[實作筆記] 盤點與清理自訂網域的 Email DNS 殘留（用郵件 header 當偵探）</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>自架 n8n 跑在 GCP VM 的 Docker container，<br>某天備份 log 出現這行：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error tracking disabled because this release is older than 6 weeks.</span><br></pre></td></tr></table></figure><p>意思是 n8n 的 release 超過六週就會關掉錯誤追蹤，提醒你該更新了。<br>趁機整理一下 Docker 版 n8n 的更新流程。</p><hr><h2 id="更新前先確認現況"><a href="#更新前先確認現況" class="headerlink" title="更新前先確認現況"></a>更新前先確認現況</h2><h3 id="確認目前版本"><a href="#確認目前版本" class="headerlink" title="確認目前版本"></a>確認目前版本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> n8n n8n --version</span><br><span class="line"><span class="comment"># 2.16.1</span></span><br></pre></td></tr></table></figure><h3 id="確認-container-設定"><a href="#確認-container-設定" class="headerlink" title="確認 container 設定"></a>確認 container 設定</h3><p>更新要重建 container，所以要先抓出原本的設定：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">docker inspect n8n --format <span class="string">&#x27;&#123;&#123;json .HostConfig.PortBindings&#125;&#125;&#x27;</span></span><br><span class="line"><span class="comment"># &#123;&quot;5678/tcp&quot;:[&#123;&quot;HostIp&quot;:&quot;&quot;,&quot;HostPort&quot;:&quot;5678&quot;&#125;]&#125;</span></span><br><span class="line"></span><br><span class="line">docker inspect n8n --format <span class="string">&#x27;&#123;&#123;range .Mounts&#125;&#125;&#123;&#123;.Source&#125;&#125;:&#123;&#123;.Destination&#125;&#125;&#123;&#123;end&#125;&#125;&#x27;</span></span><br><span class="line"><span class="comment"># /home/user/.n8n:/home/node/.n8n</span></span><br><span class="line"></span><br><span class="line">docker inspect n8n --format <span class="string">&#x27;&#123;&#123;range .Config.Env&#125;&#125;&#123;&#123;println .&#125;&#125;&#123;&#123;end&#125;&#125;&#x27;</span></span><br><span class="line"><span class="comment"># N8N_SECURE_COOKIE=false</span></span><br><span class="line"><span class="comment"># ...（其他是 image 預設的，不用帶）</span></span><br></pre></td></tr></table></figure><p>重點是找出三個東西：<strong>port</strong>、<strong>bind mount 路徑</strong>、<strong>自己設的 env</strong>。</p><hr><h2 id="更新三步驟"><a href="#更新三步驟" class="headerlink" title="更新三步驟"></a>更新三步驟</h2><h3 id="Step-1：pull-最新-image"><a href="#Step-1：pull-最新-image" class="headerlink" title="Step 1：pull 最新 image"></a>Step 1：pull 最新 image</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull docker.n8n.io/n8nio/n8n</span><br></pre></td></tr></table></figure><h3 id="Step-2：停掉舊-container"><a href="#Step-2：停掉舊-container" class="headerlink" title="Step 2：停掉舊 container"></a>Step 2：停掉舊 container</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker stop n8n &amp;&amp; docker <span class="built_in">rm</span> n8n</span><br></pre></td></tr></table></figure><p>這時候 n8n 停機，資料不會消失，因為資料在 VM 本地的 bind mount 目錄，<br>跟 container 的生命週期完全無關。</p><h3 id="Step-3：用相同設定重新啟動"><a href="#Step-3：用相同設定重新啟動" class="headerlink" title="Step 3：用相同設定重新啟動"></a>Step 3：用相同設定重新啟動</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">  --name n8n \</span><br><span class="line">  --restart unless-stopped \</span><br><span class="line">  -p 5678:5678 \</span><br><span class="line">  -v /home/user/.n8n:/home/node/.n8n \</span><br><span class="line">  -e N8N_SECURE_COOKIE=<span class="literal">false</span> \</span><br><span class="line">  docker.n8n.io/n8nio/n8n</span><br></pre></td></tr></table></figure><p>把 <code>-v</code> 的路徑換成你自己的 bind mount 路徑，env 只帶你自己加的就好。</p><hr><h2 id="驗證"><a href="#驗證" class="headerlink" title="驗證"></a>驗證</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> n8n n8n --version</span><br><span class="line">docker ps | grep n8n</span><br></pre></td></tr></table></figure><p>確認版本號，確認 container 狀態是 <code>Up</code>，完成。</p><hr><h2 id="補充：備份"><a href="#補充：備份" class="headerlink" title="補充：備份"></a>補充：備份</h2><p>更新前要不要備份？</p><p>如果你有設 bind mount，資料就在 VM 本地，container 停掉不影響。<br>但如果更新後 n8n 有 migration 問題，舊 DB 可能回不去。</p><p>保險做法是更新前先手動跑一次備份：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 匯出所有 workflow</span></span><br><span class="line">docker <span class="built_in">exec</span> n8n n8n <span class="built_in">export</span>:workflow --all --pretty \</span><br><span class="line">  --output=/home/node/.n8n/exports/workflows/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 匯出所有 credentials</span></span><br><span class="line">docker <span class="built_in">exec</span> n8n n8n <span class="built_in">export</span>:credentials --all --pretty \</span><br><span class="line">  --output=/home/node/.n8n/exports/credentials/</span><br></pre></td></tr></table></figure><p>或者搭一個 cron job 每天自動備份、git push，就不用每次更新前手動跑：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># crontab -e</span></span><br><span class="line">0 3 * * * /home/user/n8n-backup/backup.sh &gt;&gt; /var/log/n8n-backup.log 2&gt;&amp;1</span><br></pre></td></tr></table></figure><hr><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><p>Docker 版 n8n 更新流程：</p><ol><li><code>docker pull</code> — 拉新 image</li><li><code>docker stop n8n &amp;&amp; docker rm n8n</code> — 移除舊 container</li><li><code>docker run</code> — 用相同設定重建</li></ol><p>停機時間大約 30 秒。<br>關鍵是 bind mount：資料住在 VM，不住在 container，container 砍掉重練資料不受影響。</p><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/05/28/2026/update-n8n-docker-on-gcp-vm/</id>
    <link href="https://blog.marsen.me/2026/05/28/2026/update-n8n-docker-on-gcp-vm/"/>
    <published>2026-05-28T16:42:57.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>自架 n8n 跑在 GCP VM 的 Docker container，<br>某天備份 log 出現這行：</p>
<fi]]>
    </summary>
    <title>[實作筆記] 更新自架在 GCP VM 上的 n8n（Docker 版）</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="工具筆記" scheme="https://blog.marsen.me/tags/%E5%B7%A5%E5%85%B7%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>最近在電商專案要把虛構金流換成真實串接，比較了 TapPay、綠界、藍新三家。</p><p>比著發現原來存在著兩種金流串接的模式。</p><ul><li><strong>Prime Token 模式</strong>：TapPay、Stripe、Braintree</li><li><strong>MPG(Merchant Payment Gateway) 模式</strong>：綠界 ECPay、藍新 NewebPay</li></ul><p>兩種模式選錯，後面整個訂單系統的設計都會錯。</p><hr><h2 id="本質差異就一個問題：卡號從哪走？"><a href="#本質差異就一個問題：卡號從哪走？" class="headerlink" title="本質差異就一個問題：卡號從哪走？"></a>本質差異就一個問題：卡號從哪走？</h2><p>兩種模式的根本問題只有一個：<strong>敏感卡號（PAN + CVV）的傳遞路徑</strong>。</p><p>MPG 型金流（綠界、藍新跳轉）對消費者反而更透明——畫面明確跳到金流商的頁面，消費者看得出來自己在哪。</p><p>但是在使用者體驗上，會多一個斷點，以電商來說會有轉換率的問題</p><p>Prime 型（TapPay iframe）的取捨是體驗好但透明度低，信任靠的是商家品牌，不是技術可見性。</p><p>而這是一個取捨。</p><hr><h2 id="實作細節"><a href="#實作細節" class="headerlink" title="實作細節"></a>實作細節</h2><p>Prime Token 的卡號欄位內嵌結帳頁（實際是 iframe，能改邊框字體但限制多）。</p><p>整個結帳流程在你自己的頁面，設計語言、外觀風格一致。</p><p>MPG 直接跳到金流方頁面，<strong>你的品牌設計到那一刻全部斷掉</strong>。</p><p>但好處也很實際：同一個金流頁面能直接接信用卡 + ATM + 超商代碼 + LINE Pay。</p><p>兩者的開發量比較</p><p>Prime Token 你寫的東西：</p><ul><li>前端 SDK 載入 + mount 欄位</li><li>後端一支 charge API</li><li>一個結果頁</li></ul><p>MPG 你多寫的東西：</p><ul><li>表單產生器（含簽章演算法）</li><li>自動 submit 的轉址頁</li><li><strong>NotifyURL endpoint</strong>（驗章 + 冪等 + 更新訂單，這支是核心）</li><li><strong>ReturnURL endpoint</strong>（使用者回站只看結果，<strong>不可以</strong>當付款依據）</li><li>訂單狀態機要多 <code>awaiting_payment</code></li><li>對帳邏輯（callback 沒到怎辦？要定時 query 嗎？）</li></ul><p>MPG 看似簡單，實際工程量比 Prime Token 大，<strong>主要是 callback 的 edge case 多</strong>。</p><h2 id="補充-PCI-DSS-是什麼"><a href="#補充-PCI-DSS-是什麼" class="headerlink" title="補充: PCI DSS 是什麼 ?"></a>補充: PCI DSS 是什麼 ?</h2><p>Payment Card Industry Data Security Standard，簡稱 PCI DSS。</p><p>信用卡組織（Visa、Mastercard 等）聯合制定的資安標準，規定任何「碰到卡號」的系統都要符合一堆安全要求。</p><p>核心概念：碰到卡號的範圍越小，你要過的關越少。</p><p>實務上只有金流商或大型電商會去取得這種認証，一般而言小型電商還是透過第三方金流來實現交易。</p><hr><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>兩種模式的本質差異是卡號的傳遞路徑：Prime Token 走 SDK iframe，MPG 走表單轉址</li><li>信任是最難的</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/05/21/2026/payment-gateway-prime-token-vs-mpg/</id>
    <link href="https://blog.marsen.me/2026/05/21/2026/payment-gateway-prime-token-vs-mpg/"/>
    <published>2026-05-21T03:36:44.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>最近在電商專案要把虛構金流換成真實串接，比較了 TapPay、綠界、藍新三家。</p>
<p>比著發現原來存在著兩種金流串接]]>
    </summary>
    <title>[工具筆記] Prime Token vs MPG：兩種台灣電商金流模式的本質差異</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>用 LINE Bot 做系統通知，結果某天發現通知完全停了，重啟也沒用。<br>查了一圈才發現根本原因不是程式問題，這篇記錄怎麼查用量、以及為什麼停的。</p><hr><h2 id="問題現象"><a href="#問題現象" class="headerlink" title="問題現象"></a>問題現象</h2><p>LINE Bot 的 push 通知在某個時間點之後完全靜默。<br>程式沒報錯，因為程式碼裡的 push 用了 <code>.catch(() =&gt; {})</code> 吃掉所有錯誤：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">push</span>(<span class="params">text: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> lineClient.<span class="title function_">pushMessage</span>(<span class="variable constant_">ALLOWED_USER_ID</span>, msg).<span class="title function_">catch</span>(<span class="function">() =&gt;</span> &#123;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>靜默失敗，完全看不出來哪裡出問題。</p><hr><h2 id="怎麼查用量"><a href="#怎麼查用量" class="headerlink" title="怎麼查用量"></a>怎麼查用量</h2><p>LINE 的用量統計不在 Developers Console，要去 <strong>LINE Official Account Manager</strong>：</p><ol><li>打開 <a href="https://manager.line.biz/">manager.line.biz</a></li><li>選你的 Official Account</li><li>左側 → <strong>分析</strong> → <strong>訊息</strong></li></ol><p>可以看到：</p><table><thead><tr><th>欄位</th><th>說明</th></tr></thead><tbody><tr><td>合計（所有訊息）</td><td>Reply + Push 總和</td></tr><tr><td>Push</td><td>主動推播的則數</td></tr><tr><td>Reply</td><td>回覆訊息的則數</td></tr></tbody></table><p>可以選日期範圍，最長 60 天。</p><hr><h2 id="免費額度是多少"><a href="#免費額度是多少" class="headerlink" title="免費額度是多少"></a>免費額度是多少</h2><p>LINE Messaging API 免費方案每月有 <strong>500 則</strong>免費訊息（Push + Reply 合計）。</p><p>超過之後每則需要付費，或者訊息會被擋下來（依帳號設定而定）。</p><p>500 則聽起來很多，但如果 Bot 跑 AI 對話，Claude 回應很長，常常一次就切成好幾則 push（每則上限 5000 字），很快就會耗光。</p><hr><h2 id="這次的真正原因"><a href="#這次的真正原因" class="headerlink" title="這次的真正原因"></a>這次的真正原因</h2><p>查完用量才發現：5&#x2F;1 用了 141 則，5&#x2F;2 用了 59 則，5&#x2F;3 之後全部是 0。</p><p>5&#x2F;3 之後不是「發了但被擋」，是根本沒有觸發 webhook。</p><p>原因是 LINE Bot 用 <strong>Webhook 模式</strong>，需要一個對外的 HTTPS URL。<br>我的架構是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LINE 伺服器 → HTTPS Webhook URL → Cloudflare Tunnel → 本機 Express server</span><br></pre></td></tr></table></figure><p>Cloudflare Tunnel 的 launchd service 在 5&#x2F;3 掛掉，LINE 打不到 webhook，所以什麼都沒發生。</p><hr><h2 id="Telegram-為什麼更穩"><a href="#Telegram-為什麼更穩" class="headerlink" title="Telegram 為什麼更穩"></a>Telegram 為什麼更穩</h2><p>Telegram 用 <strong>Long Polling</strong> 模式，bot 主動去 Telegram 伺服器拉訊息，不需要對外開放任何 URL。</p><table><thead><tr><th></th><th>LINE</th><th>Telegram</th></tr></thead><tbody><tr><td>連線模式</td><td>Webhook（需要 tunnel）</td><td>Long Polling（不需要）</td></tr><tr><td>斷線風險</td><td>tunnel 掛、IP 變、PORT 未開</td><td>幾乎沒有</td></tr><tr><td>免費額度</td><td>500 則&#x2F;月</td><td>無限制</td></tr></tbody></table><p>如果是用來做系統通知（不可中斷），Telegram 比 LINE 穩定很多。</p><hr><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>LINE 用量在 LINE Official Account Manager → 分析 → 訊息</li><li>免費方案 500 則&#x2F;月，用完靜默失敗</li><li>Webhook 模式依賴 tunnel，tunnel 掛掉通知就停</li><li>對穩定性要求高的通知場景，Telegram Long Polling 更適合</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/05/06/2026/line-messaging-api-quota/</id>
    <link href="https://blog.marsen.me/2026/05/06/2026/line-messaging-api-quota/"/>
    <published>2026-05-06T11:00:49.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>用 LINE Bot 做系統通知，結果某天發現通知完全停了，重啟也沒用。<br>查了一圈才發現根本原因不是程式問題，這篇記錄]]>
    </summary>
    <title>[實作筆記] LINE Messaging API 用量查詢：免費額度怎麼看，以及為什麼通知會停止</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="AI生成" scheme="https://blog.marsen.me/tags/AI%E7%94%9F%E6%88%90/"/>
    <content>
      <![CDATA[<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul><li><a href="https://www.theverge.com/ai-artificial-intelligence/922113/pentagon-ai-classified-openai-google-nvidia">五角大廈與 OpenAI、Google 及 Nvidia 簽署機密 AI 合約，但排除 Anthropic</a>：美國國防部與三大科技巨頭達成機密協議，旨在將先進 AI 技術用於國防安全。</li><li><a href="https://www.theverge.com/news/921944/microsoft-word-legal-agent-ai">微軟希望律師信任其在 Word 文件中推出的新 AI Agent</a>：Microsoft 針對法律專業人士推出專用 AI 代理，協助處理複雜的法律文書工作。</li><li><a href="https://www.theverge.com/tech/917225/sam-altman-elon-musk-openai-lawsuit">Elon Musk 與 Sam Altman 關於 OpenAI 未來的法庭法律戰即時更新</a>：針對 OpenAI 是否偏離非營利初衷的世紀訴訟持續開庭，影響 AI 產業未來走向。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/922947/roomba-creator-new-robot-familiar-machines-magic-ai-launch">Roomba 創始人帶著毛茸茸的 AI 陪伴機器人回歸</a>：Familiar Machines 發表名為 Magic 的機器人，主打具備情感互動與 AI 學習能力的居家陪伴。</li><li><a href="https://www.theverge.com/column/921599/ai-music-is-flooding-streaming-services-but-who-wants-it">AI 音樂正淹沒串流服務——但究竟誰想聽？</a>：分析 AI 生成音軌在各大串流平台激增的現狀，以及市場對於非人類創作音樂的反應。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/923684/musk-brockman-altman-openai-trial">OpenAI 總裁做了一切，除了「回答問題」</a>：Greg Brockman 在與 Musk 的訴訟庭審中，對於關鍵轉型問題表現出迴避態度。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/920881/ai-generated-bible-videos-christian-creators-fiverr-slop">基督教內容創作者正將 AI 垃圾內容外包給 Fiverr 上的零工人員</a>：部分創作者利用低廉勞動力在 Fiverr 製作大量低品質的 AI 生成宗教影片以博取流量。</li></ul><h2 id="其他訊息"><a href="#其他訊息" class="headerlink" title="其他訊息"></a>其他訊息</h2><ul><li><a href="https://www.theverge.com/tech/921690/dualshot-recorder-iphone-camera-app-derrick-downey-jr">DualShot Recorder 如何成為 2026 年最熱門的相機 App</a></li><li><a href="https://www.theverge.com/ai-artificial-intelligence/920775/evidence-exhibits-elon-musk-sam-altman-openai-trial">目前為止在 Musk 對決 Altman 案中揭露的所有證據</a></li><li><a href="https://www.theverge.com/podcast/922009/musk-openai-trial-testimony-vergecast">Elon Musk 在法庭上度過了糟糕的一週</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/05/04/2026/ai-weekly-20260505/</id>
    <link href="https://blog.marsen.me/2026/05/04/2026/ai-weekly-20260505/"/>
    <published>2026-05-04T16:00:52.000Z</published>
    <summary>
      <![CDATA[<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul>
<li><a href="https://www.theverge.com/ai-artificial-intellig]]>
    </summary>
    <title>[AI生成]20260505 周報</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="AI生成" scheme="https://blog.marsen.me/tags/AI%E7%94%9F%E6%88%90/"/>
    <content>
      <![CDATA[<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul><li><a href="https://www.theverge.com/ai-artificial-intelligence/922113/pentagon-ai-classified-openai-google-nvidia">五角大廈與 OpenAI、Google 及 Nvidia 達成機密 AI 協議，但不包括 Anthropic</a>：美國國防部與三家科技巨頭簽署軍事用途的機密 AI 合約，而 Anthropic 則未在名單之列。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/921546/elon-musk-xai-openai-trial-model-distillation">Elon Musk 證實 xAI 曾使用 OpenAI 的模型來訓練 Grok</a>：在法庭證詞中，Musk 承認 xAI 曾利用競爭對手的模型數據進行蒸餾訓練。</li><li><a href="https://www.theverge.com/news/921944/microsoft-word-legal-agent-ai">Microsoft 希望律師能信任 Word 文件中的新 AI 代理</a>：Microsoft 推出專為法律專業設計的 AI 功能，旨在協助處理複雜的法律文件與合約審閱。</li><li><a href="https://www.theverge.com/column/921599/ai-music-is-flooding-streaming-services-but-who-wants-it">AI 音樂正淹沒串流服務——但究竟誰想要這些音樂？</a>：探討當前串流平台充斥大量 AI 生成音軌的現象，以及市場對這些內容的真實需求。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/920775/evidence-exhibits-elon-musk-sam-altman-openai-trial">Musk 對決 Altman 案中目前揭露的所有證據</a>：整理了這場攸關 OpenAI 未來走向的世紀訴訟中，目前公開的所有關鍵證物與通訊記錄。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/920881/ai-generated-bible-videos-christian-creators-fiverr-slop">基督教內容創作者正將 AI 垃圾內容外包給 Fiverr 上的零工</a>：揭露創作者利用廉價勞動力結合 AI 工具，大量產出低品質宗教影片以賺取流量的生態。</li><li><a href="https://www.theverge.com/tech/921690/dualshot-recorder-iphone-camera-app-derrick-downey-jr">網路最受歡迎的松鼠爸爸如何打造 2026 年最火紅的相機 App</a>：介紹網紅 Derrick Downey Jr. 開發的 DualShot Recorder 如何在競爭激烈的 App 市場脫穎而出。</li></ul><h2 id="其他訊息"><a href="#其他訊息" class="headerlink" title="其他訊息"></a>其他訊息</h2><ul><li><a href="https://www.theverge.com/podcast/922009/musk-openai-trial-testimony-vergecast">Elon Musk 在法庭度過了糟糕的一週</a></li><li><a href="https://www.theverge.com/ai-artificial-intelligence/921713/musk-v-altman-jared-birchall-screw-up-xai">Musk 對決 Altman 案中最瘋狂的部分發生在陪審團離場時</a></li><li><a href="https://www.theverge.com/tech/917225/sam-altman-elon-musk-openai-lawsuit">Elon Musk 與 Sam Altman 爭奪 OpenAI 未來之法律戰的即時更新</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/05/03/2026/ai-weekly-20260504/</id>
    <link href="https://blog.marsen.me/2026/05/03/2026/ai-weekly-20260504/"/>
    <published>2026-05-03T16:00:59.000Z</published>
    <summary>
      <![CDATA[<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul>
<li><a href="https://www.theverge.com/ai-artificial-intellig]]>
    </summary>
    <title>[AI生成]20260504 周報</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="AI生成" scheme="https://blog.marsen.me/tags/AI%E7%94%9F%E6%88%90/"/>
    <content>
      <![CDATA[<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul><li><a href="https://www.briefs.co/news/uber-torches-entire-2026-ai-budget-on-claude-code-in-four-months/">Uber 在四個月內將 2026 年全年的 AI 預算花光在 Claude Code 上</a>：Uber 因過度使用 Claude Code 進行開發，僅花費四個月就耗盡了原定一整年的 AI 算力預算。</li><li><a href="https://semgrep.dev/blog/2026/malicious-dependency-in-pytorch-lightning-used-for-ai-training/">在 PyTorch Lightning AI 訓練函式庫中發現以「沙蟲（Shai-Hulud）」為名的惡意軟體</a>：研究人員在主流 AI 訓練工具中發現惡意依賴包，恐威脅開發者的開發環境安全。</li><li><a href="https://deepmind.google/research/publications/231971/">抽象謬誤：為什麼 AI 只能模擬而無法實例化意識</a>：DeepMind 發表論文探討 AI 與意識的本質區別，強調模擬智慧並不等同於擁有真實意識。</li><li><a href="https://www.promptarmor.com/resources/ramps-sheets-ai-exfiltrates-financials">Ramp 的 Sheets AI 洩漏財務數據</a>：安全研究指出 Ramp 整合的表格 AI 工具存在漏洞，可能導致敏感的企業財務資訊外流。</li><li><a href="https://www.bbc.com/news/articles/c5yerr4m1yno">Spotify 推出「已驗證」徽章以區分真人藝術家與 AI</a>：為了應對 AI 生成音樂的洪流，Spotify 開始標記真人歌手以保障創作者權益與聽眾辨識度。</li><li><a href="https://www.theverge.com/ai-artificial-intelligence/920401/gen-z-ai">年輕人使用 AI 的次數越多，就越討厭它</a>：最新調查顯示 Z 世代對 AI 的排斥感隨著使用頻率增加，反映出對生成內容質量的疲勞與擔憂。</li><li><a href="https://simonwillison.net/2026/Apr/30/zig-anti-ai/">Zig 專案對其反 AI 貢獻政策的合理解釋</a>：知名程式語言 Zig 團隊說明了為何禁止 AI 生成的程式碼貢獻，旨在維護程式碼庫的純淨與長期可維護性。</li><li><a href="https://www.dailygrail.com/2026/05/the-claude-delusion-richard-dawkins-believes-his-female-ai-chatbot-is-conscious/">Claude 幻覺：Richard Dawkins 認為他的 AI 聊天機器人具有意識</a>：演化生物學家 Richard Dawkins 聲稱他的 Claude 客製化機器人表現出了意識跡象，引發科學界與哲學界的激辯。</li></ul><h2 id="其他訊息"><a href="#其他訊息" class="headerlink" title="其他訊息"></a>其他訊息</h2><ul><li><a href="https://arxiv.org/abs/2509.00462">AI 自我偏好在演算法招聘中的應用：實證證據與洞察</a></li><li><a href="https://mljar.com/">Mljar Studio – 本地 AI 數據分析師，可將分析結果儲存為 Notebooks</a></li><li><a href="https://copilot.simplepdf.com/?share=a7d00ad073c75a75d493228e6ff7b11eb3f2d945b6175913e87898ec96ca8076&form=w9&lang=en">使用客戶端工具調用通過 AI 填充 PDF 表單</a></li><li><a href="https://github.com/lahfir/agent-desktop">Agent-desktop – 專為 AI Agent 設計的原生桌面自動化 CLI</a></li><li><a href="https://fusion.adam.new/install">AI CAD Harness</a></li><li><a href="https://californiawaterblog.com/2026/04/26/ai-water-use-distractions-and-lessons-for-california/">AI 的用水量比公眾想像的要少</a></li><li><a href="https://github.com/leox255/loopsy">Loopsy：讓不同機器上的終端與 AI Agent 溝通的方式</a></li><li><a href="https://datacenter.fm/">DataCenter.FM – 收錄 AI 泡沫環境音的背景噪音 App</a></li><li><a href="https://status.claude.com/incidents/2gf1jpyty350">Claude.ai 與 API 服務中斷通知（已修復）</a></li><li><a href="https://mikeoss.com/">Mike：開源法律 AI</a></li><li><a href="https://handyai.substack.com/p/your-ceo-is-suffering-from-ai-psychosis">你的執行長正患有 AI 精神官能症</a></li><li><a href="https://migrainebrain.bearblog.dev/people-who-dont-use-ai-will-be-left-behind/">「不用 AI 的人將會被拋在腦後」</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/05/02/2026/ai-weekly-20260503/</id>
    <link href="https://blog.marsen.me/2026/05/02/2026/ai-weekly-20260503/"/>
    <published>2026-05-02T15:20:23.000Z</published>
    <summary>
      <![CDATA[<h2 id="本周要點"><a href="#本周要點" class="headerlink" title="本周要點"></a>本周要點</h2><ul>
<li><a href="https://www.briefs.co/news/uber-torches-entire-]]>
    </summary>
    <title>[AI生成]20260502 周報</title>
    <updated>2026-06-12T11:37:08.400Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>n8n 跑起來、workflow 也在跑了，下一個問題：掛掉怎麼辦？</p><p>VM 重開機沒問題，但容器砍掉重建、或換 VM，workflow 和 credentials 就都要重設。<br>這篇記錄怎麼用 CLI export + git 做自動備份。</p><hr><h2 id="備份方案決策"><a href="#備份方案決策" class="headerlink" title="備份方案決策"></a>備份方案決策</h2><p>考慮過幾個方向：</p><table><thead><tr><th>方案</th><th>問題</th></tr></thead><tbody><tr><td>備份整個 <code>.n8n</code> 目錄</td><td>SQLite 每次執行都寫入，幾乎天天變動，太肥</td></tr><tr><td>只備份 workflow JSON</td><td>沒有 credentials 無法完整還原</td></tr><tr><td>n8n Source Control（GitOps）</td><td>Enterprise&#x2F;Business 付費功能，Community 版不支援</td></tr><tr><td>CLI export workflow + credentials</td><td>可行，輕量，Community 版都有</td></tr></tbody></table><p>最終選 <strong>CLI export + GitHub private repo</strong>，搭配 cron job 每天自動跑。</p><hr><h2 id="架構"><a href="#架構" class="headerlink" title="架構"></a>架構</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">GCP VM（cron job 每天凌晨 3 點）</span><br><span class="line">  └─ docker exec n8n → export workflow + credentials JSON</span><br><span class="line">       └─ bind mount（/home/user/.n8n）</span><br><span class="line">            └─ 複製到 repo backup/YYYYMMDD-NNN/ → git commit + push → GitHub</span><br></pre></td></tr></table></figure><p>每次跑都建立新的日期資料夾（<code>20260430-001</code>、<code>20260430-002</code>），保留最近 30 份，舊的自動刪除。就算刪了，git log 還是查得到歷史。</p><hr><h2 id="實作"><a href="#實作" class="headerlink" title="實作"></a>實作</h2><h3 id="Repo-結構"><a href="#Repo-結構" class="headerlink" title="Repo 結構"></a>Repo 結構</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Marsen.Butler.n8n.Backup/</span><br><span class="line">├── README.md</span><br><span class="line">├── install.sh    ← 一次性安裝，設定 cron job</span><br><span class="line">├── backup.sh     ← 每次執行的備份邏輯</span><br><span class="line">└── backup/</span><br><span class="line">    └── 20260430-001/</span><br><span class="line">        ├── workflows/</span><br><span class="line">        └── credentials/</span><br></pre></td></tr></table></figure><h3 id="backup-sh-核心邏輯"><a href="#backup-sh-核心邏輯" class="headerlink" title="backup.sh 核心邏輯"></a>backup.sh 核心邏輯</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># container name 預設 n8n，可用環境變數覆蓋</span></span><br><span class="line">N8N_CONTAINER=<span class="string">&quot;<span class="variable">$&#123;N8N_CONTAINER:-n8n&#125;</span>&quot;</span></span><br><span class="line">KEEP_COUNT=<span class="string">&quot;<span class="variable">$&#123;KEEP_COUNT:-30&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 產生日期流水號資料夾</span></span><br><span class="line">DATE_TAG=<span class="string">&quot;<span class="subst">$(date +%Y%m%d)</span>&quot;</span></span><br><span class="line">SERIAL=$(<span class="built_in">printf</span> <span class="string">&quot;%03d&quot;</span> $(( $(ls -d backup/<span class="variable">$&#123;DATE_TAG&#125;</span>-* <span class="number">2</span>&gt;/dev/null | wc -l) + <span class="number">1</span> )))</span><br><span class="line">BACKUP_DIR=<span class="string">&quot;backup/<span class="variable">$&#123;DATE_TAG&#125;</span>-<span class="variable">$&#123;SERIAL&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 確認容器在跑，export，複製到 repo...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 刪除舊備份（保留最近 KEEP_COUNT 份）</span></span><br><span class="line">TOTAL=$(<span class="built_in">ls</span> -d backup/[0-9]* 2&gt;/dev/null | <span class="built_in">wc</span> -l)</span><br><span class="line"><span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$TOTAL</span>&quot;</span> -gt <span class="string">&quot;<span class="variable">$KEEP_COUNT</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">ls</span> -d backup/[0-9]* | <span class="built_in">sort</span> | <span class="built_in">head</span> -n $(( TOTAL - KEEP_COUNT )) | xargs <span class="built_in">rm</span> -rf</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 每次都 commit</span></span><br><span class="line">git add backup/</span><br><span class="line">git commit -m <span class="string">&quot;backup <span class="variable">$&#123;DATE_TAG&#125;</span>-<span class="variable">$&#123;SERIAL&#125;</span>&quot;</span></span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p><code>docker inspect</code> 自動找 bind mount 路徑，不寫死，換 VM 也通用。</p><h3 id="install-sh"><a href="#install-sh" class="headerlink" title="install.sh"></a>install.sh</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 設定每天凌晨 3 點執行</span></span><br><span class="line">CRON_JOB=<span class="string">&quot;0 3 * * * <span class="variable">$&#123;BACKUP_SCRIPT&#125;</span> &gt;&gt; /var/log/n8n-backup.log 2&gt;&amp;1&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 避免重複寫入</span></span><br><span class="line"><span class="keyword">if</span> crontab -l 2&gt;/dev/null | grep -q <span class="string">&quot;<span class="variable">$BACKUP_SCRIPT</span>&quot;</span>; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;Cron job already exists, skipping.&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  (crontab -l 2&gt;/dev/null; <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$CRON_JOB</span>&quot;</span>) | crontab -</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><hr><h2 id="踩坑記錄"><a href="#踩坑記錄" class="headerlink" title="踩坑記錄"></a>踩坑記錄</h2><h3 id="git-clone-下來的腳本沒有執行權限"><a href="#git-clone-下來的腳本沒有執行權限" class="headerlink" title="git clone 下來的腳本沒有執行權限"></a>git clone 下來的腳本沒有執行權限</h3><p><code>git pull</code> 之後直接跑 <code>./backup.sh</code>，結果：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-bash: ./backup.sh: Permission denied</span><br></pre></td></tr></table></figure><p>原因：git 預設不保留 <code>chmod +x</code>，clone 下來的檔案是 644。</p><p>解法：用 <code>git update-index</code> 把執行權限記進 git：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git update-index --<span class="built_in">chmod</span>=+x backup.sh install.sh</span><br><span class="line">git commit -m <span class="string">&quot;fix: preserve execute permission for scripts in git&quot;</span></span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>之後 <code>git pull</code> 拿到的檔案就自帶執行權限，不需要手動 <code>chmod</code>。</p><h3 id="Container-name-不應該硬綁定"><a href="#Container-name-不應該硬綁定" class="headerlink" title="Container name 不應該硬綁定"></a>Container name 不應該硬綁定</h3><p>一開始寫死 <code>N8N_CONTAINER=&quot;n8n&quot;</code>，但我的容器叫 <code>n8n-01</code>，直接報錯。</p><p>改為環境變數覆蓋，預設值 <code>n8n</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">N8N_CONTAINER=<span class="string">&quot;<span class="variable">$&#123;N8N_CONTAINER:-n8n&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure><p>臨時要換：<code>N8N_CONTAINER=n8n-01 ./backup.sh</code>，不需要改腳本。</p><h3 id="備份會把-token-明文存進-git"><a href="#備份會把-token-明文存進-git" class="headerlink" title="備份會把 token 明文存進 git"></a>備份會把 token 明文存進 git</h3><p>備份的是 workflow JSON，而 workflow JSON 裡如果 token 是直接寫在節點裡，那備份就等於把 token 存進 git repo。</p><p>用文字搜尋就找得到：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep -r <span class="string">&quot;access_token\|IGAA&quot;</span> backup/</span><br></pre></td></tr></table></figure><p><strong>解法：改用 n8n Credential 存 token</strong></p><p>n8n 的 Credential 有獨立加密機制，workflow JSON 裡只會存 Credential 的 ID，不含實際值。這樣備份出來的 workflow 就不會洩漏 token。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 不好（硬寫在節點）</span><br><span class="line">&quot;access_token&quot;: &quot;IGAAYLng3...&quot;</span><br><span class="line"></span><br><span class="line"># 好（引用 Credential）</span><br><span class="line">&quot;credentials&quot;: &#123; &quot;instagramOAuth2Api&quot;: &#123; &quot;id&quot;: &quot;xxx&quot;, &quot;name&quot;: &quot;IG&quot; &#125; &#125;</span><br></pre></td></tr></table></figure><p>Credential 本身也可以 export 備份，但它是加密的（用 <code>N8N_ENCRYPTION_KEY</code>），只要 Key 沒洩漏，備份裡的 Credential 就是安全的。</p><hr><h2 id="VM-上安裝步驟"><a href="#VM-上安裝步驟" class="headerlink" title="VM 上安裝步驟"></a>VM 上安裝步驟</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. clone repo</span></span><br><span class="line">git <span class="built_in">clone</span> git@github.com:marsen/Marsen.Butler.n8n.Backup.git ~/n8n-backup</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 安裝 cron job</span></span><br><span class="line"><span class="built_in">cd</span> ~/n8n-backup</span><br><span class="line">./install.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 手動跑一次確認</span></span><br><span class="line">./backup.sh</span><br></pre></td></tr></table></figure><hr><h2 id="還原"><a href="#還原" class="headerlink" title="還原"></a>還原</h2><h3 id="情境一：同-VM，換新容器"><a href="#情境一：同-VM，換新容器" class="headerlink" title="情境一：同 VM，換新容器"></a>情境一：同 VM，換新容器</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">  --name n8n \</span><br><span class="line">  -e N8N_SECURE_COOKIE=<span class="literal">false</span> \</span><br><span class="line">  -v /home/user/.n8n:/home/node/.n8n \</span><br><span class="line">  -p 5678:5678 \</span><br><span class="line">  --restart unless-stopped \</span><br><span class="line">  docker.n8n.io/n8nio/n8n</span><br><span class="line"></span><br><span class="line"><span class="comment"># 取最新備份</span></span><br><span class="line">LATEST=$(<span class="built_in">ls</span> -d ~/n8n-backup/backup/[0-9]* | <span class="built_in">sort</span> | <span class="built_in">tail</span> -1)</span><br><span class="line"><span class="built_in">cp</span> -r <span class="string">&quot;<span class="variable">$&#123;LATEST&#125;</span>/workflows/.&quot;</span> /home/user/.n8n/exports/workflows/</span><br><span class="line"><span class="built_in">cp</span> -r <span class="string">&quot;<span class="variable">$&#123;LATEST&#125;</span>/credentials/.&quot;</span> /home/user/.n8n/exports/credentials/</span><br><span class="line">docker <span class="built_in">exec</span> n8n n8n import:workflow --separate --input=/home/node/.n8n/exports/workflows/</span><br><span class="line">docker <span class="built_in">exec</span> n8n n8n import:credentials --separate --input=/home/node/.n8n/exports/credentials/</span><br></pre></td></tr></table></figure><h3 id="情境二：新-VM"><a href="#情境二：新-VM" class="headerlink" title="情境二：新 VM"></a>情境二：新 VM</h3><p>從密碼管理器取出 <code>N8N_ENCRYPTION_KEY</code>，啟動時帶入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">  --name n8n \</span><br><span class="line">  -e N8N_ENCRYPTION_KEY=你的key \</span><br><span class="line">  -e N8N_SECURE_COOKIE=<span class="literal">false</span> \</span><br><span class="line">  -v /home/user/.n8n:/home/node/.n8n \</span><br><span class="line">  -p 5678:5678 \</span><br><span class="line">  --restart unless-stopped \</span><br><span class="line">  docker.n8n.io/n8nio/n8n</span><br></pre></td></tr></table></figure><p>clone repo 並還原最新備份：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> git@github.com:marsen/Marsen.Butler.n8n.Backup.git ~/n8n-backup</span><br><span class="line">LATEST=$(<span class="built_in">ls</span> -d ~/n8n-backup/backup/[0-9]* | <span class="built_in">sort</span> | <span class="built_in">tail</span> -1)</span><br><span class="line"><span class="built_in">cp</span> -r <span class="string">&quot;<span class="variable">$&#123;LATEST&#125;</span>/workflows/.&quot;</span> /home/user/.n8n/exports/workflows/</span><br><span class="line"><span class="built_in">cp</span> -r <span class="string">&quot;<span class="variable">$&#123;LATEST&#125;</span>/credentials/.&quot;</span> /home/user/.n8n/exports/credentials/</span><br><span class="line">docker <span class="built_in">exec</span> n8n n8n import:workflow --separate --input=/home/node/.n8n/exports/workflows/</span><br><span class="line">docker <span class="built_in">exec</span> n8n n8n import:credentials --separate --input=/home/node/.n8n/exports/credentials/</span><br></pre></td></tr></table></figure><blockquote><p>Credentials 是用 <code>N8N_ENCRYPTION_KEY</code> 加密儲存，還原時必須用相同的 Key，否則全部無法解密。Key 存密碼管理器，不存 repo。</p></blockquote><hr><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/n8n-1-in-gcp-free-tier-vm/">個人自動化平台(一) n8n &amp; GCP VM</a></li><li><a href="/2026/n8n-2-cloudflared-and-tunnel/">個人自動化平台(二) Cloudflare Access &amp; Cloudflare Tunnel</a></li><li><a href="/2026/n8n-3-pipeline-overview/">個人自動化平台(三) 收集、處理、輸出：三層可插拔管道設計</a></li><li><a href="/2026/n8n-3.1-rebuild-infromation/">個人自動化平台(番外) 拆掉重建 GCP VM &amp; Cloudflared Tunnel &amp; Cloudflare Access</a></li><li><a href="/2026/n8n-4-pipeline-fetch/">個人自動化平台(四) n8n 實作：收集層，RSS 資料來源</a></li><li><a href="/2026/n8n-5-pipeline-process/">個人自動化平台(五) n8n 實作：處理層，Aggregate + Gemini 週報整理</a></li><li><a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六) n8n 實作：輸出層，Instagram API 取得 Token 全記錄</a></li><li><a href="/2026/n8n-6.1-google-oauth-redirect-uri-debug/">個人自動化平台(番外) Google OAuth redirect_uri_mismatch 除錯記錄</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>CLI export 是 Community 版可用的最輕量備份方案</li><li>每次建日期資料夾（<code>YYYYMMDD-NNN</code>），保留最近 30 份，超過自動 prune</li><li>就算資料夾被刪，git log 還是查得到歷史</li><li><code>git update-index --chmod=+x</code> 讓腳本執行權限跟著 repo 走</li><li><code>N8N_CONTAINER</code> 環境變數讓腳本不寫死 container name</li><li>還原關鍵：<code>N8N_ENCRYPTION_KEY</code> 要另外存好</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/29/2026/n8n-7-backup-automation/</id>
    <link href="https://blog.marsen.me/2026/04/29/2026/n8n-7-backup-automation/"/>
    <published>2026-04-29T19:08:11.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>n8n 跑起來、workflow 也在跑了，下一個問題：掛掉怎麼辦？</p>
<p>VM 重開機沒問題，但容器砍掉重建、或換]]>
    </summary>
    <title>[實作筆記] 個人自動化平台(七) n8n 備份自動化：workflow + credentials → GitHub</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>整理一次技術面試的對話紀錄，主題圍繞在大流量電商系統的設計與應變。</p><p><strong>面試公司</strong>：H 社，北台灣，科技服務業，員工數十人。實際業務為線上遊戲平台的開發與維運；產品涵蓋自研遊戲、第三方遊戲接入，以及面向代理商與玩家的平台系統。整體採微服務架構，開發部門數十人，小組制運作。跨國營運，技術端設於台灣。</p><p><strong>職位</strong>：後端為主的全端工程師，技術棧含 Node.js &#x2F; Go、現代前端框架、SQL &#x2F; NoSQL，要求具備架構設計與 CI&#x2F;CD 實務能力，並承擔技術指導職責。</p><p>以下按主題整理成 Q&amp;A，並附上現代技術視角供參考。</p><hr><h2 id="大流量與快取架構"><a href="#大流量與快取架構" class="headerlink" title="大流量與快取架構"></a>大流量與快取架構</h2><p><strong>Q：電商搶購時流量瞬間暴增，怎麼擋？</strong></p><p>先接受「讓使用者排隊，不讓網站 crash」這個前提，整個設計才會清楚。</p><p>具體做法是多層 Cache 疊加：</p><ul><li><strong>前端</strong>：搶購期間關掉非核心的資訊（推薦、廣告等），減少後端壓力</li><li><strong>後端第一層</strong>：庫存資料放 Redis</li><li><strong>後端第二層</strong>：使用 MemoryCache（.NET 4.5&#x2F;4.6 內建）做本地快取</li><li><strong>DB 層</strong>：DB 自身的查詢快取</li></ul><p>流量依序被每一層擋掉，真正打到 DB 的請求量才能控制住。</p><p><strong>現代解法</strong>：Kubernetes + HPA（Horizontal Pod Autoscaler）根據 CPU &#x2F; Memory 自動擴縮容，不再需要預先備機和人工值班。Redis 依然是標準選擇，但搭配 Lua Script 或 WATCH&#x2F;MULTI&#x2F;EXEC 可更精確控制庫存的原子操作，避免超賣。</p><hr><p><strong>Q：那時候沒有 Kubernetes，怎麼做水平擴充？</strong></p><p>用 VM 搭配手動的負載分流。備用機平時待機，搶購前預先準備好；緊急時把流量導過去。</p><p>沒有自動 scale，所以需要人工值班監控——雙 11 期間三班制，值班加 on-call 全員待命。</p><p><strong>現代解法</strong>：Kubernetes 的 Deployment + HPA 讓水平擴充自動化，節省人力成本。搭配 Cluster Autoscaler，甚至連底層節點都能自動增減，不再需要預先開好備用機等待。</p><hr><h2 id="監控與事故應對"><a href="#監控與事故應對" class="headerlink" title="監控與事故應對"></a>監控與事故應對</h2><p><strong>Q：系統是 crash 了才知道，還是有提前預警？</strong></p><p>有兩個觸發條件：</p><ol><li><strong>資源閾值警報</strong>：CPU 或 Memory 超過 80% 就通知，不等到崩潰</li><li><strong>錯誤日誌頻率</strong>：同類型錯誤在短時間內高頻出現，自動告警</li></ol><p>這些監控都是自己開發的，那個年代沒有現成的 APM 工具可以直接套。</p><p><strong>現代解法</strong>：Prometheus + Grafana 是現在的標準配置，幾乎不需要自己開發。資源閾值警報用 AlertManager 設定即可；錯誤頻率則可搭配 ELK Stack 或 Loki，對 log 做 rate 統計觸發告警。SaaS 選項如 Datadog、New Relic 更是開箱即用。</p><hr><p><strong>Q：真的發生過事故嗎？當下怎麼處理？</strong></p><p>有發生過，包含主機掛掉切備用機、備用機跟著掛掉的連環情況。</p><p>第一線的原則是：<strong>先讓系統回來，問題找根因可以之後再說</strong>。</p><p>當下操作是執行預先備好的備案（切流量、重啟服務），同時通報讓有能力解決根本問題的人起來處理。</p><p>事後改進：正式搶購活動前一定先壓測，模擬預估流量的 110%，只測核心購物流程，把能關的功能全部關掉。</p><p><strong>現代解法</strong>：事故應對流程本身可以標準化為 <strong>Runbook</strong>，記錄每種已知情境的處置步驟，讓值班工程師不需要靠記憶應急。Kubernetes 搭配 liveness&#x2F;readiness probe 可自動偵測服務不健康並重啟，減少人工介入的時間窗口。壓測工具如 k6 或 Locust 可整合進 CI&#x2F;CD，讓每次上線前自動跑一輪基準測試。</p><hr><h2 id="金流系統設計"><a href="#金流系統設計" class="headerlink" title="金流系統設計"></a>金流系統設計</h2><p><strong>Q：對接多個金流廠商，程式碼怎麼不變成一團亂？</strong></p><p>用 Strategy Pattern 的概念，把金流流程切成 Workflow。</p><p>整個下單流程是一條工作站鍊：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">商品計算（折扣、贈品）→ 訂單建立 → 金流付款</span><br></pre></td></tr></table></figure><p>金流工作站本身的邏輯很薄：根據使用者選的付款方式，決定走哪個 Strategy。不同廠商（信用卡、超商取貨、銀行轉帳）各自封裝成獨立模組，主流程不在意裡面怎麼串接。</p><p>新增金流廠商：加一個新模組，主流程不用改。</p><p><strong>現代解法</strong>：這個架構今天依然適用，核心概念沒有過時。如果系統更大，可以把每個金流廠商拆成獨立的微服務，透過統一的 Payment Gateway API 對外暴露，主流程完全不感知廠商差異。設計模式上除了 Strategy，也可以考慮 Plugin Architecture，讓新廠商直接以套件形式掛入，不需要改動核心程式碼。</p><hr><p><strong>Q：訂單成立了，金流卻失敗，怎麼處理？</strong></p><p>訂單成立和金流付款是兩個獨立的 Transaction。</p><p>金流失敗不會 rollback 訂單，而是讓訂單保持「待付款」狀態，通知使用者重新付款或換付款方式。</p><p>好處是：金流失敗是常見的可預期錯誤，這樣設計使用者體驗比較好，也避免訂單資料不一致。</p><p><strong>現代解法</strong>：在微服務架構下，這個問題升級為<strong>分散式交易</strong>問題。現代標準解法是 <strong>Saga Pattern</strong>：每個步驟成功後發事件觸發下一步，失敗時執行 compensating transaction 回滾前面的狀態，整個流程不依賴跨服務的 ACID Transaction。Choreography Saga 適合步驟簡單的流程，Orchestration Saga 適合需要集中管控的複雜流程。</p><hr><h2 id="AI-與本地部署"><a href="#AI-與本地部署" class="headerlink" title="AI 與本地部署"></a>AI 與本地部署</h2><p><strong>Q：你做的法律 AI 系統，為什麼不用雲端大模型就好？</strong></p><p>客戶是大型企業（Sony、政府機關、醫療機構），他們不願意把公司內部資料送上雲端。</p><p>做法是把 AI 系統打包進實體機器，送進客戶的內網部署。功能類似 ChatGPT，但跑在客戶自己的機房裡。</p><p>主要應用場景是：</p><ol><li><strong>RAG</strong>：上傳法律文件，AI 從裡面查詢相關資訊</li><li><strong>專利風險分析</strong>：原本需要半年人工查詢各大專利資料庫，自動化後大幅縮短</li></ol><p><strong>現代工具</strong>：本地部署 AI 的技術棧已經成熟許多。<strong>Ollama</strong> 可以一鍵跑主流開源模型（Llama 3、Mistral 等），<strong>LlamaIndex</strong> 或 <strong>LangChain</strong> 提供 RAG pipeline 的標準建構塊，向量資料庫用 <strong>ChromaDB</strong>、<strong>Qdrant</strong> 或 <strong>Weaviate</strong> 都可以，整個 stack 都可以打包進客戶內網。對於需要更強模型的場景，也可以考慮 <strong>vLLM</strong> 搭配企業級 GPU，在不上雲的前提下跑到生產等級的推論效能。</p><hr><h2 id="關於-AI-對開發的影響"><a href="#關於-AI-對開發的影響" class="headerlink" title="關於 AI 對開發的影響"></a>關於 AI 對開發的影響</h2><p><strong>Q：你覺得 AI 工具對軟體開發的影響是什麼？</strong></p><p>一個工程師現在能產出的東西，可能相當於十年前一整個小團隊的產出。</p><p>但這也帶來新的問題：程式碼產出速度快了，但架構決策、程式碼品質的門檻反而更重要。AI 可以快速生出大量程式碼，但如果架構設計不對，技術債也會累積得更快。</p><p><strong>進一步的觀察</strong>：Coding style 和命名規範這類問題，現在完全可以交給 AI 處理——把 guideline 寫好，讓 AI 執行和稽核就好，不值得花人力在上面爭論。真正需要人把關的是 <strong>Domain 層和 Use Case 層的設計</strong>：商業邏輯有沒有被正確封裝？Unit Test 有沒有覆蓋核心行為？這兩層做對了，架構就不容易爛掉。六邊形架構（Hexagonal Architecture）或 Clean Architecture 在 AI 輔助開發的時代反而更值得落地，因為邊界清楚，AI 才不容易在錯誤的地方塞邏輯。</p><hr><h2 id="面試反思"><a href="#面試反思" class="headerlink" title="面試反思"></a>面試反思</h2><p><strong>用業界術語包裝已知的答案</strong></p><p>描述分散式交易的處理方式時，邏輯是對的，但沒有說出 <strong>Saga Pattern</strong> 這個詞。面試官如果熟悉這個詞，會更快建立信心；說不出來反而讓人以為是靠直覺摸索，不是系統性的設計知識。同樣的，提到 Request ID 追蹤時，可以直接說 <strong>Distributed Tracing</strong>、<strong>OpenTelemetry</strong>，展示對現代標準的認識。術語不是裝飾，它是讓對方快速理解你程度的捷徑。</p><p><strong>主動帶出現代解法</strong></p><p>談電商 N 社那個年代的做法時，可以補一句「現在我會用 Kubernetes + HPA 處理這個問題」——展示持續學習的態度。技術知識有，但沒有主動說出來，廣度就沒有完整呈現。</p><blockquote><p>這兩點對應的技術題目值得另外整理成文章研究：Saga Pattern、Distributed Tracing、OpenTelemetry。</p></blockquote><hr><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>大流量不靠單一技術解決，是多層 Cache + 系統開關 + 人工值班的組合</li><li>監控要在系統崩潰前就觸發，資源閾值和錯誤頻率是兩個實用的指標</li><li>事故處理分兩階段：先讓系統回來，再找根因</li><li>金流複雜性用 Strategy Pattern + Workflow 拆解，廠商細節封裝進模組</li><li>本地 AI 部署是企業安全性需求的實際解法，不只是技術選擇</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/28/2026/interview-high-traffic-system-design/</id>
    <link href="https://blog.marsen.me/2026/04/28/2026/interview-high-traffic-system-design/"/>
    <published>2026-04-28T09:01:57.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>整理一次技術面試的對話紀錄，主題圍繞在大流量電商系統的設計與應變。</p>
<p><strong>面試公司</strong>]]>
    </summary>
    <title>
      <![CDATA[[實作筆記] 技術面試 Q&A：大流量電商系統設計實戰]]>
    </title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>在 <a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六)</a> 實作 IG Token 工具時，</p><p>為了修 Instagram OAuth callback URL 的問題，在 Vercel 設了 <code>NEXT_PUBLIC_BASE_URL</code>。</p><p>沒想到這個動作讓 Google 登入炸掉，報 <code>redirect_uri_mismatch</code>。</p><p>表面上是「連帶損傷」，但根本原因是 <strong>production 的 Google 登入從來沒測試過</strong>，問題早就存在，只是這次才被發現。</p><hr><h2 id="事件時間線"><a href="#事件時間線" class="headerlink" title="事件時間線"></a>事件時間線</h2><h3 id="背景：production-Google-登入從來沒運作過"><a href="#背景：production-Google-登入從來沒運作過" class="headerlink" title="背景：production Google 登入從來沒運作過"></a>背景：production Google 登入從來沒運作過</h3><p><code>NEXT_PUBLIC_BASE_URL</code> 在 Vercel 上沒有設值時，<code>env.baseUrl</code> 是 <code>undefined</code>，Google OAuth 送出的 redirect URI 是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">undefined/api/auth/google/callback</span><br></pre></td></tr></table></figure><p>這個字串不可能通過 Google 驗證。</p><p>但 production 上從來沒有人測試過 Google 登入，所以一直沒被發現。</p><p>本機開發設的是 <code>NEXT_PUBLIC_BASE_URL=http://localhost:3000</code>，Google Console 也只登記了 <code>http://localhost:3000/api/auth/google/callback</code>，本機正常，所以沒有警覺。</p><h3 id="起點：IG-Token-工具的-Instagram-OAuth-callback-URL-錯誤"><a href="#起點：IG-Token-工具的-Instagram-OAuth-callback-URL-錯誤" class="headerlink" title="起點：IG Token 工具的 Instagram OAuth callback URL 錯誤"></a>起點：IG Token 工具的 Instagram OAuth callback URL 錯誤</h3><p>實作 IG Token 工具時（<code>demo.marsen.me</code>），Instagram 授權完成後 callback 沒有導回正確的頁面，而是跑到 Vercel 內部 URL。</p><p>原因：Instagram callback route 的 <code>baseUrl</code> 在 Vercel 上沒有設定，fallback 讀到的是 Vercel 執行環境的 host，不是對外的 <code>demo.marsen.me</code>。</p><h3 id="修法：在-Vercel-設定-NEXT-PUBLIC-BASE-URL"><a href="#修法：在-Vercel-設定-NEXT-PUBLIC-BASE-URL" class="headerlink" title="修法：在 Vercel 設定 NEXT_PUBLIC_BASE_URL"></a>修法：在 Vercel 設定 <code>NEXT_PUBLIC_BASE_URL</code></h3><p>在 Vercel 環境變數加上：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">NEXT_PUBLIC_BASE_URL=https://demo.marsen.me</span><br></pre></td></tr></table></figure><p>因為專案用 <code>vercel build --prebuilt</code> 方案，直接 Redeploy 不會重新 build，必須 push 新的空 commit 觸發 GitHub Actions：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git commit --allow-empty -m <span class="string">&quot;chore: trigger redeploy&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><p>Instagram OAuth callback 正常了。</p><h3 id="問題浮現：第一次在-production-測試-Google-登入"><a href="#問題浮現：第一次在-production-測試-Google-登入" class="headerlink" title="問題浮現：第一次在 production 測試 Google 登入"></a>問題浮現：第一次在 production 測試 Google 登入</h3><p>設了 <code>NEXT_PUBLIC_BASE_URL</code> 後，第一次真正在 production 試 Google 登入。</p><p><code>NEXT_PUBLIC_BASE_URL</code> 同時影響 Google OAuth 的 redirect URI（<code>src/app/api/auth/google/route.ts</code>）：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> google = <span class="keyword">new</span> <span class="title class_">Google</span>(</span><br><span class="line">  env.<span class="property">googleClientId</span>,</span><br><span class="line">  env.<span class="property">googleClientSecret</span>,</span><br><span class="line">  <span class="string">`<span class="subst">$&#123;env.baseUrl&#125;</span>/api/auth/google/callback`</span>,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>app 送出的 redirect_uri 變成：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://demo.marsen.me/api/auth/google/callback</span><br></pre></td></tr></table></figure><p>但 Google Cloud Console 只登記了本機的那條：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://localhost:3000/api/auth/google/callback</span><br></pre></td></tr></table></figure><p>不符，報 <code>redirect_uri_mismatch</code>。</p><hr><h2 id="症狀"><a href="#症狀" class="headerlink" title="症狀"></a>症狀</h2><p>點「Google 登入」後，Google 跳出錯誤頁面：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">已封鎖存取權：這個應用程式的要求無效</span><br><span class="line">發生錯誤 400：redirect_uri_mismatch</span><br></pre></td></tr></table></figure><hr><h2 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h2><p>進 Google Cloud Console → Credentials → OAuth 2.0 Client → Authorized redirect URIs，新增：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://demo.marsen.me/api/auth/google/callback</span><br></pre></td></tr></table></figure><p>本機那條 <code>http://localhost:3000/api/auth/google/callback</code> 保留，本機開發還用得到。</p><p>存檔幾分鐘內生效，不需要重新 build 或 deploy。</p><hr><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六) n8n 實作：輸出層，Instagram API 取得 Token 全記錄</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>根本原因不是「設定改壞了」，而是 <strong>production Google 登入從來沒被測試過</strong>，問題一直存在</li><li>IG Token 工具需要在 Vercel 設 <code>NEXT_PUBLIC_BASE_URL</code>，這個動作讓潛藏的問題第一次浮現</li><li><code>redirect_uri_mismatch</code> 代表 app 送出的 redirect URI 和 Google Console 登記的不符</li><li><code>NEXT_PUBLIC_BASE_URL</code> 影響所有 OAuth 服務，改了就要同步更新各 Console 的 redirect URI</li><li>Google Console 可以同時登記多條 URI，本機和 production 各一條，互不影響</li><li>用 <code>vercel build --prebuilt</code> 的專案，env var 更新後要 push 空 commit 才會生效</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/27/2026/n8n-6.1-google-oauth-redirect-uri-debug/</id>
    <link href="https://blog.marsen.me/2026/04/27/2026/n8n-6.1-google-oauth-redirect-uri-debug/"/>
    <published>2026-04-27T15:28:15.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>在 <a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六)</a> 實作 IG]]>
    </summary>
    <title>[實作筆記] 個人自動化平台(番外) Google OAuth redirect_uri_mismatch 除錯記錄</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>收集層跑通後，每次執行輸出統一格式的文章列表。</p><p>這篇記錄處理層：把多筆文章合併，送給 Gemini 整理成 AI 週報。</p><p>未來也可能抽換成不同的雲端或地端 AI 模型服務。</p><hr><h2 id="為什麼要-Aggregate？"><a href="#為什麼要-Aggregate？" class="headerlink" title="為什麼要 Aggregate？"></a>為什麼要 Aggregate？</h2><p>收集層輸出的是多筆獨立資料（以 RSS 為例，一次可能拿到 10 筆）。</p><p>Basic LLM Chain 預設對每筆各送一次請求，10 筆 &#x3D; 10 次 API call，超過 Gemini 免費版 5 RPM 限制。</p><p>用 <strong>Aggregate</strong> 節點先把多筆合成一筆，再送一次請求給 AI。這也更符合「週報」的本意——要的是一份完整報告，不是 10 篇獨立摘要。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Edit Fields → Aggregate（All Item Data）→ Basic LLM Chain（Google Gemini）</span><br></pre></td></tr></table></figure><hr><h2 id="Prompt-設計"><a href="#Prompt-設計" class="headerlink" title="Prompt 設計"></a>Prompt 設計</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">週報日期：&#123;&#123; $now.toFormat(&#x27;yyyy年MM月dd日&#x27;) &#125;&#125;</span><br><span class="line"></span><br><span class="line">以下是本週 AI 新聞，請用繁體中文整理成一份週報。</span><br><span class="line">每則新聞說明「是什麼」和「為什麼重要」，條列呈現。</span><br><span class="line"></span><br><span class="line">&#123;&#123; $json.data.map(i =&gt; `【$&#123;i.source&#125;】$&#123;i.title&#125;\n$&#123;i.content&#125;`).join(&#x27;\n\n&#x27;) &#125;&#125;</span><br></pre></td></tr></table></figure><p>執行結果：日期正確，內容有條理，每篇清楚說明「是什麼」和「為什麼重要」。</p><hr><h2 id="踩坑：Rate-Limit"><a href="#踩坑：Rate-Limit" class="headerlink" title="踩坑：Rate Limit"></a>踩坑：Rate Limit</h2><p>第一次直接把 10 筆送給 LLM，遇到 <code>429 Too Many Requests</code>：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">quota exceeded: limit 5 requests/minute (gemini-2.5-flash free tier)</span><br></pre></td></tr></table></figure><p>解法：加 Aggregate 節點合併成一筆，從 10 次請求降為 1 次，問題消失。</p><hr><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/n8n-1-in-gcp-free-tier-vm/">個人自動化平台(一) n8n &amp; GCP VM</a></li><li><a href="/2026/n8n-2-cloudflared-and-tunnel/">個人自動化平台(二) Cloudflare Access &amp; Cloudflare Tunnel</a></li><li><a href="/2026/n8n-3-pipeline-overview/">個人自動化平台(三) 收集、處理、輸出：三層可插拔管道設計</a></li><li><a href="/2026/n8n-3.1-rebuild-infromation/">個人自動化平台(番外) 拆掉重建 GCP VM &amp; Cloudflared Tunnel &amp; Cloudflare Access</a></li><li><a href="/2026/n8n-4-pipeline-fetch/">個人自動化平台(四) n8n 實作：收集層，RSS 資料來源</a></li><li><a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六) n8n 實作：輸出層，Instagram API 取得 Token 全記錄</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>Aggregate 節點把多筆合成一筆，是處理層的關鍵，解決 rate limit 也符合週報語意</li><li>Prompt 用 expression 動態帶入日期與新聞內容，輸出穩定</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/27/2026/n8n-5-pipeline-process/</id>
    <link href="https://blog.marsen.me/2026/04/27/2026/n8n-5-pipeline-process/"/>
    <published>2026-04-27T08:03:36.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>收集層跑通後，每次執行輸出統一格式的文章列表。</p>
<p>這篇記錄處理層：把多筆文章合併，送給 Gemini 整理成 A]]>
    </summary>
    <title>[實作筆記] 個人自動化平台(五) n8n 實作：處理層，Aggregate + Gemini 週報整理</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>收集層（RSS 抓資料）和處理層（Gemini 整理週報）都跑通了，接下來要實作輸出層：把 AI 整理好的內容自動發布到社群媒體。</p><p>這篇記錄從「選哪個平台」到「拿到 Instagram Long-lived Token」的完整過程，包含踩到的坑和解法。</p><hr><h2 id="平台選擇"><a href="#平台選擇" class="headerlink" title="平台選擇"></a>平台選擇</h2><p>想把 AI 週報自動發布到社群媒體，先評估三個選項：</p><table><thead><tr><th>平台</th><th>純文字</th><th>API</th><th>帳號門檻</th><th>難度</th></tr></thead><tbody><tr><td>Instagram</td><td>不行（必須圖片）</td><td>有</td><td>Business 或 Creator</td><td>高</td></tr><tr><td>YouTube 影片</td><td>不行（必須影片）</td><td>有</td><td>普通 Google 帳號</td><td>最高</td></tr><tr><td>YouTube Community</td><td>可以</td><td><strong>無</strong></td><td>500+ 訂閱</td><td>—</td></tr><tr><td>LinkedIn</td><td>可以</td><td>有（需審查）</td><td>需要 Page</td><td>中</td></tr><tr><td>Telegram 公開頻道</td><td>可以</td><td>最簡單</td><td>無</td><td>低</td></tr></tbody></table><p>最終決定：<strong>Instagram</strong>。不是因為它最簡單——Telegram 才是——而是因為這是我實際在用的平台，想把週報發在自己的帳號上。但 IG 必須有圖片，輸出層要加圖片生成步驟。</p><hr><h2 id="IG-帳號準備"><a href="#IG-帳號準備" class="headerlink" title="IG 帳號準備"></a>IG 帳號準備</h2><p>Instagram API 發文需要 <strong>Business 或 Creator 帳號</strong>。個人帳號的 Basic Display API 已於 2024 年 12 月關閉。</p><p>切換 Creator 帳號（免費，不需要 Facebook Page）：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IG App → 設定和隱私 → 帳號類型和工具 → 切換為專業帳號 → 創作者</span><br></pre></td></tr></table></figure><p>切換後保留所有粉絲和貼文，只是解鎖 API 權限。</p><hr><h2 id="Facebook-Developer-App-設定"><a href="#Facebook-Developer-App-設定" class="headerlink" title="Facebook Developer App 設定"></a>Facebook Developer App 設定</h2><h3 id="App-類型要選對"><a href="#App-類型要選對" class="headerlink" title="App 類型要選對"></a>App 類型要選對</h3><p>建 App 時類型有坑：</p><ul><li><strong>Consumer</strong>：給舊版 Basic Display API 用，已關閉，選了找不到 Instagram Graph API</li><li><strong>Business</strong>：正確選項，支援 Instagram Graph API content publishing</li></ul><p>前往 <a href="https://developers.facebook.com/apps/create">developers.facebook.com&#x2F;apps&#x2F;create</a>，選 <strong>Business</strong>。</p><h3 id="Instagram-API-流程選擇"><a href="#Instagram-API-流程選擇" class="headerlink" title="Instagram API 流程選擇"></a>Instagram API 流程選擇</h3><p>進 App 後找 Instagram 產品，有兩種 API 流程：</p><table><thead><tr><th></th><th>Instagram Login</th><th>Facebook Login</th></tr></thead><tbody><tr><td>支援 Creator 帳號</td><td>是</td><td>需要連 FB Page</td></tr><tr><td>需要 Facebook Page</td><td>不需要</td><td>需要</td></tr></tbody></table><p>選 <strong>API setup with Instagram business login</strong>（Instagram Login 流程）。</p><h3 id="加-Tester-帳號"><a href="#加-Tester-帳號" class="headerlink" title="加 Tester 帳號"></a>加 Tester 帳號</h3><p>Step 1 要先在 Roles 頁籤加 Instagram Tester：</p><ol><li>左側選單 → App Roles → Instagram Testers</li><li>輸入 IG username 送出邀請</li><li>在畫面下方找到連結，點開到 <code>https://www.instagram.com/accounts/manage_access/</code> 接受邀請</li></ol><hr><h2 id="踩雷筆記：OAuth-黑-白畫面-bug"><a href="#踩雷筆記：OAuth-黑-白畫面-bug" class="headerlink" title="踩雷筆記：OAuth 黑&#x2F;白畫面 bug"></a>踩雷筆記：OAuth 黑&#x2F;白畫面 bug</h2><p>點 <strong>Generate token</strong> 後，Instagram 授權頁面出現黑畫面或白畫面，token 無法顯示。</p><p>這是 Instagram Developer Console 已知的 rendering bug。OAuth 授權其實已完成，authorization code 在 redirect URL 裡，只是 UI 沒渲染出來。</p><h3 id="解法：webhook-site-當臨時-Redirect-URI"><a href="#解法：webhook-site-當臨時-Redirect-URI" class="headerlink" title="解法：webhook.site 當臨時 Redirect URI"></a>解法：webhook.site 當臨時 Redirect URI</h3><p><strong>步驟一</strong>：前往 <a href="https://webhook.site/">webhook.site</a>，取得唯一 URL，例如：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://webhook.site/98ec2192-c62e-459e-848a-334b370887f0</span><br></pre></td></tr></table></figure><p><strong>步驟二</strong>：加到 Instagram App → Step 3 → OAuth Redirect URLs。</p><p><strong>步驟三</strong>：手動打開授權 URL（替換 <code>YOUR_WEBHOOK_URL</code>）：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://www.instagram.com/oauth/authorize?client_id=APP_ID&amp;redirect_uri=YOUR_WEBHOOK_URL&amp;response_type=code&amp;scope=instagram_business_basic,instagram_business_content_publish</span><br></pre></td></tr></table></figure><p><strong>步驟四</strong>：Instagram 授權後 redirect 到 webhook.site，從 URL 取得 <code>code=</code> 後面的值。</p><p><strong>步驟五</strong>：馬上換 short-lived token（code 幾分鐘過期）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">curl -X POST https://api.instagram.com/oauth/access_token \</span><br><span class="line">  -F client_id=APP_ID \</span><br><span class="line">  -F client_secret=APP_SECRET \</span><br><span class="line">  -F grant_type=authorization_code \</span><br><span class="line">  -F redirect_uri=YOUR_WEBHOOK_URL \</span><br><span class="line">  -F <span class="string">&quot;code=YOUR_CODE&quot;</span></span><br></pre></td></tr></table></figure><p><strong>步驟六</strong>：換 long-lived token（有效 60 天）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -X GET <span class="string">&quot;https://graph.instagram.com/access_token?grant_type=ig_exchange_token&amp;client_secret=APP_SECRET&amp;access_token=SHORT_LIVED_TOKEN&quot;</span></span><br></pre></td></tr></table></figure><p>回傳範例：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;access_token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;IGAAYLng3...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;token_type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bearer&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;expires_in&quot;</span><span class="punctuation">:</span> <span class="number">5165491</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Token 更新（60 天到期前跑一次即可）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -X GET <span class="string">&quot;https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&amp;access_token=LONG_LIVED_TOKEN&quot;</span></span><br></pre></td></tr></table></figure><hr><h3 id="加速解-IG-Token-Generator-工具"><a href="#加速解-IG-Token-Generator-工具" class="headerlink" title="加速解: IG Token Generator 工具"></a>加速解: IG Token Generator 工具</h3><p>OAuth 流程太繁瑣，自製工具放在 <code>demo.marsen.me/admin/tools/ig-token</code>，下次取 token 不需要手動操作。</p><p>目前只有我能使用，如果有人有興趣可以與我聯絡我再視情況開放工具，具體還是希望 Bug 可以修複</p><p><strong>技術棧</strong>：Next.js 16 App Router</p><p><strong>流程設計</strong>：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">使用者輸入 App ID + App Secret</span><br><span class="line">  → POST /api/instagram/start</span><br><span class="line">    → server 存 session cookie（5 分鐘 TTL）</span><br><span class="line">    → 回傳 Instagram OAuth URL</span><br><span class="line">  → 瀏覽器跳轉到 Instagram 授權</span><br><span class="line">  → Instagram redirect 到 /api/instagram/callback</span><br><span class="line">    → server 讀 cookie 取得 App Secret</span><br><span class="line">    → 換 short-lived token → 換 long-lived token</span><br><span class="line">    → redirect 到工具頁顯示結果</span><br></pre></td></tr></table></figure><p>App Secret 全程只在 server-side 流通，不暴露給客戶端。</p><p>待改進項目</p><ul><li><strong>範例資料不用真實值</strong>：截圖或 UI 展示時，input 欄位的 placeholder 不應出現真實 App ID，用假資料代替</li><li><strong>Input 欄位短暫快取</strong>：App ID 和 App Secret 用 <code>sessionStorage</code> 快取 15～30 分鐘，避免每次授權都要重填（注意 App Secret 快取有資安風險，要考慮清楚）</li><li><strong>Token 長期快取 90 天</strong>：取得 long-lived token 後存到 <code>localStorage</code>，90 天內重開頁面自動顯示，不需重新授權</li></ul><hr><h2 id="踩雷筆記：Vercel-部署的-prebuilt-限制"><a href="#踩雷筆記：Vercel-部署的-prebuilt-限制" class="headerlink" title="踩雷筆記：Vercel 部署的 prebuilt 限制"></a>踩雷筆記：Vercel 部署的 prebuilt 限制</h2><p>這個專案用 GitHub Actions 做 CI&#x2F;CD，採 <code>vercel build --prebuilt</code> 方案：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># GitHub Actions</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">run:</span> <span class="string">vercel</span> <span class="string">build</span> <span class="string">--prod</span>    <span class="comment"># Actions 裡 build</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">run:</span> <span class="string">vercel</span> <span class="string">deploy</span> <span class="string">--prebuilt</span> <span class="string">--prod</span>  <span class="comment"># 上傳 build 產出物</span></span><br></pre></td></tr></table></figure><p>更新 Vercel 環境變數後，<strong>不能直接 Redeploy</strong>，因為 Vercel 沒有原始碼可重新 build。必須 push 新 commit 觸發 GitHub Actions 重新 build 才能套用新設定：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git commit --allow-empty -m <span class="string">&quot;chore: trigger redeploy&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><p>prebuilt 的好處是 CI 與 deploy 分離，確保 lint&#x2F;type-check&#x2F;test 通過才部署；代價是 env var 更新要重新 build。</p><hr><h2 id="踩雷筆記：Meta-廣告帳號被限制"><a href="#踩雷筆記：Meta-廣告帳號被限制" class="headerlink" title="踩雷筆記：Meta 廣告帳號被限制"></a>踩雷筆記：Meta 廣告帳號被限制</h2><p>20260427 處理中</p><p>測試期間收到 Meta 通知：「Your professional ad account is restricted from advertising」。</p><p>第一反應是懷疑跟自動化發文有關，查了一下：這個限制針對的是<strong>廣告功能</strong>（投放廣告、Meta Pixel、推廣貼文），不影響 Content Publishing API。用官方 API 發文是 Meta 明確允許的行為，沒有違規。</p><p>推測原因：新開 Business App 首次觸發系統自動審查，誤判。已按「Request a Review」申訴中。</p><p>API 發文功能在申訴期間不受影響，繼續正常運作。</p><p>20260429 通過，帳號恢復正常</p><hr><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/n8n-1-in-gcp-free-tier-vm/">個人自動化平台(一) n8n &amp; GCP VM</a></li><li><a href="/2026/n8n-2-cloudflared-and-tunnel/">個人自動化平台(二) Cloudflare Access &amp; Cloudflare Tunnel</a></li><li><a href="/2026/n8n-3-pipeline-overview/">個人自動化平台(三) 收集、處理、輸出：三層可插拔管道設計</a></li><li><a href="/2026/n8n-3.1-rebuild-infromation/">個人自動化平台(番外) 拆掉重建 GCP VM &amp; Cloudflared Tunnel &amp; Cloudflare Access</a></li><li><a href="/2026/n8n-4-pipeline-fetch/">個人自動化平台(四) n8n 實作：收集層，RSS 資料來源</a></li><li><a href="/2026/n8n-5-pipeline-process/">個人自動化平台(五) n8n 實作：處理層，Aggregate + Gemini 週報整理</a></li><li><a href="/2026/n8n-6.1-google-oauth-redirect-uri-debug/">個人自動化平台(番外) Google OAuth redirect_uri_mismatch 除錯記錄</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>IG API 只支援 Business 或 Creator 帳號，個人帳號的 Basic Display API 已關閉</li><li>Facebook Developer App 類型要選 Business，流程選 Instagram Login（不需要 FB Page）</li><li>Developer Console 的 token generator 有 bug，用 webhook.site 當臨時 redirect URI 繞過</li><li>Long-lived token 有效 60 天，到期前用 refresh API 更新即可</li><li>自製工具把 OAuth 流程自動化，之後取 token 一鍵搞定</li></ul><p>（待補：圖片生成 + 實際 IG 發文流程）</p><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/22/2026/n8n-6-pipeline-output/</id>
    <link href="https://blog.marsen.me/2026/04/22/2026/n8n-6-pipeline-output/"/>
    <published>2026-04-22T18:14:41.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>收集層（RSS 抓資料）和處理層（Gemini 整理週報）都跑通了，接下來要實作輸出層：把 AI 整理好的內容自動發布到社群]]>
    </summary>
    <title>[實作筆記] 個人自動化平台(六) n8n 實作：輸出層，Instagram API 取得 Token 全記錄</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="產業報告" scheme="https://blog.marsen.me/tags/%E7%94%A2%E6%A5%AD%E5%A0%B1%E5%91%8A/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>2019 年，我在 <a href="https://blog.marsen.me/2019/05/18/2019/aws_deepraceer/">AWS DeepRacer 工作坊</a>裡用 Python 寫了幾行 reward function，讓一台 1:18 的小賽車學會跑彎道。</p><p>多年後，無人駕駛已經在真實道路上運作，這篇記錄一下產業現況。</p><h2 id="兩條路線"><a href="#兩條路線" class="headerlink" title="兩條路線"></a>兩條路線</h2><h3 id="Tesla：純視覺派"><a href="#Tesla：純視覺派" class="headerlink" title="Tesla：純視覺派"></a>Tesla：純視覺派</h3><p>馬斯克從第一性原理出發：人用兩隻眼睛就能開車，攝影機應該也夠。</p><p>2022 年起，Tesla 把 radar 全拔掉，FSD 只靠攝影機。</p><p>基礎理論在於道路本來就是為人類視覺設計的，攝影機加上夠大的神經網路應該能學會同樣的事。</p><p>FSD v12 起改用 end-to-end 架構——8 顆攝影機的原始影像直接輸入，輸出方向盤與油門指令，中間沒有人工規則，全靠模型自己學。</p><p>每台 Tesla 行駛時都在 shadow mode 默默預測「如果 FSD 接管會怎麼做」，遇到預測與駕駛行為不符的片段就自動上傳訓練。</p><h3 id="Waymo：多感測器派"><a href="#Waymo：多感測器派" class="headerlink" title="Waymo：多感測器派"></a>Waymo：多感測器派</h3><p>Waymo 走另一條路：LiDAR + radar + 精密地圖，多重機制確保安全。</p><p>2025 年，Waymo 達成 1 億英里全自動駕駛里程，每週 25 萬次付費乘車，是目前<strong>唯一真正商業化的無人駕駛服務</strong>。</p><h2 id="現況數據"><a href="#現況數據" class="headerlink" title="現況數據"></a>現況數據</h2><p>資料來源：<a href="https://rideobi.com/tesla/">Obi report</a>（分析 2025&#x2F;11 - 2026&#x2F;01 共 94,348 筆乘車紀錄）、Morgan Stanley 估算。</p><p>從 2025 年底的數據看：</p><p>| | Tesla Robotaxi | Waymo |<br>|　–　|　–　|　–　|<br>| 每公里車費 | $1.99 | $5.72 |<br>| 車輛硬體成本 | ~$30,000（Cybercab） | ~$200,000 |<br>| 等車時間 | 15 分鐘 | 5.7 分鐘 |<br>| 無人駕駛狀態 | 仍有安全駕駛員 | 已完全無人 |<br>| 鳳凰城獲利 | 否 | 是 |</p><h2 id="商業規模"><a href="#商業規模" class="headerlink" title="商業規模"></a>商業規模</h2><p>資料來源：<a href="https://research.contrary.com/report/tesla-waymo-and-the-great-sensor-debate">Contrary Research</a>、<a href="https://www.notateslaapp.com/news/3108/teslas-fsd-shadow-mode-what-it-is-and-how-it-improves-fsd">notateslaapp.com</a>。</p><p>這個差距的根源在<strong>商業模式決定的資料規模</strong>。</p><p>Waymo 賣服務，不賣車。他們的車是改裝的 Jaguar I-Pace，加上一堆感測器，一台 $200,000。</p><p>要擴大訓練資料，就得多派車上路，成本線性增長。</p><p>截至 2025 年初，Waymo 史上累計行駛里程約 <strong>7,100 萬英里</strong>。</p><p>Tesla 賣車給消費者。全球幾百萬台 Tesla 都在 shadow mode 默默記錄「如果 FSD 接管會怎麼做」。</p><p>每賣出一台車，消費者不只付了錢，還順便幫 Tesla 訓練模型。</p><p>到 2025 年底，Tesla FSD 累計行駛里程已超過 <strong>85 億英里</strong>，整體車隊的 shadow mode 資料更遠不止於此。</p><p>兩者相差超過 <strong>100 倍</strong>。這個差距，不是砸錢買 LiDAR 能追上的。</p><p>如果視覺派能解決惡劣天氣的弱點（Tesla FSD 在大雨、濃霧下表現仍不穩定），Tesla 幾乎必勝——因為硬體成本差了 7 倍，資料規模差了超過 100 倍。</p><p>Waymo 的策略是「先把安全做到無懈可擊，再壓低成本」。</p><p>Tesla 的策略是「先把規模做到無法追趕，再解決邊界問題」。</p><p>2026 年，這場賽局還沒有結果。</p><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><p>2019 年，我坐在工作坊裡，看著一台小車學會跑彎道，覺得強化學習很神奇，但離現實很遠。</p><p>2026 年，Robotaxi 已經在路上跑了。</p><p>技術本身不是護城河 —— 這兩家公司對產業有不同的願景看法，所以有了不同的商業模式。</p><p>可以繼續看下去，也期待台灣能早日坐到。</p><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="https://blog.marsen.me/2019/05/18/2019/aws_deepraceer/">[實作筆記] AWS DeepRacer</a></li><li><a href="https://electrek.co/2025/03/23/everyones-missing-the-point-of-the-tesla-vision-vs-lidar-wile-e-coyote-video/">Tesla FSD vision-only vs LiDAR - Electrek</a></li><li><a href="https://rideobi.com/tesla/">Waymo vs Tesla robotaxi cost comparison - Obi</a></li><li><a href="https://www.humai.blog/2026-is-the-year-of-autonomous-driving-waymo-in-10-cities-tesla-expanding-where-you-can-already-get-a-robotaxi/">2026 Is the Year of Autonomous Driving</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/22/2026/deepracer-to-robotaxi/</id>
    <link href="https://blog.marsen.me/2026/04/22/2026/deepracer-to-robotaxi/"/>
    <published>2026-04-22T08:13:21.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>2019 年，我在 <a href="https://blog.marsen.me/2019/05/18/2019/aws_]]>
    </summary>
    <title>[產業報告] 從 DeepRacer 到 Robotaxi：Tesla vs Waymo 的無人駕駛現況</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>上一篇定好了「收集、處理、輸出」三層架構，這篇開始實作第一層：收集。</p><p>目標是從四個 AI 新聞來源拉資料，輸出統一格式，交給處理層處理。</p><hr><h2 id="資料來源選擇"><a href="#資料來源選擇" class="headerlink" title="資料來源選擇"></a>資料來源選擇</h2><p>驗證過四個 RSS 來源都可用：</p><table><thead><tr><th>來源</th><th>URL</th><th>說明</th></tr></thead><tbody><tr><td>OpenAI</td><td><code>https://openai.com/news/rss.xml</code></td><td>官方，第一手產品更新</td></tr><tr><td>Google Blog AI</td><td><code>https://blog.google/innovation-and-ai/technology/ai/rss/</code></td><td>官方，涵蓋 Gemini 等產品</td></tr><tr><td>The Verge AI</td><td><code>https://www.theverge.com/rss/ai-artificial-intelligence/index.xml</code></td><td>綜合媒體，覆蓋廣</td></tr><tr><td>HackerNews</td><td><code>https://hnrss.org/newest?q=AI&amp;points=50</code></td><td>社群篩選，工程師視角</td></tr></tbody></table><blockquote><p><strong>Anthropic 目前無官方 RSS</strong>，是個缺口，之後另找方案補上。</p></blockquote><p>CLI 驗證方式：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -s -o /dev/null -w <span class="string">&quot;%&#123;http_code&#125; %&#123;url_effective&#125;\n&quot;</span> &lt;RSS_URL&gt;</span><br></pre></td></tr></table></figure><hr><h2 id="n8n-節點選擇"><a href="#n8n-節點選擇" class="headerlink" title="n8n 節點選擇"></a>n8n 節點選擇</h2><p>收集層有兩種做法：</p><table><thead><tr><th></th><th>RSS Feed Trigger + 暫存層</th><th>RSS Read + Filter</th></tr></thead><tbody><tr><td>抓取方式</td><td>增量，只抓新的</td><td>全量，每次重抓</td></tr><tr><td>重複抓取</td><td>不會</td><td>會（Filter 擋掉舊的）</td></tr><tr><td>Token 消耗</td><td>較省</td><td>稍多</td></tr><tr><td>架構複雜度</td><td>高（需外部暫存）</td><td>低（無狀態）</td></tr><tr><td>適合 sub-workflow</td><td>不適合（Trigger 節點限制）</td><td>適合</td></tr></tbody></table><h3 id="現況的選擇：RSS-Read-Filter"><a href="#現況的選擇：RSS-Read-Filter" class="headerlink" title="現況的選擇：RSS Read + Filter"></a>現況的選擇：RSS Read + Filter</h3><p>現階段優先走通整條流程，不想多依賴一個外部暫存服務。</p><p>RSS Read 是普通節點，可以被 sub-workflow 呼叫，搭配 Filter 過濾近 7 天的文章，邏輯乾淨：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Schedule Trigger → RSS Read → Filter（只保留近 7 天文章）</span><br></pre></td></tr></table></figure><p>等之後有需要優化 token 消耗，再評估加暫存層。</p><hr><h2 id="實作步驟"><a href="#實作步驟" class="headerlink" title="實作步驟"></a>實作步驟</h2><h3 id="步驟一：建立-Workflow"><a href="#步驟一：建立-Workflow" class="headerlink" title="步驟一：建立 Workflow"></a>步驟一：建立 Workflow</h3><p>新增 Workflow，命名 <code>[收集] The Verge AI</code>。</p><p>觸發節點選 <strong>Schedule Trigger</strong>，預設每天午夜執行即可（之後調整為週報頻率）。</p><h3 id="步驟二：加入-RSS-Read-節點"><a href="#步驟二：加入-RSS-Read-節點" class="headerlink" title="步驟二：加入 RSS Read 節點"></a>步驟二：加入 RSS Read 節點</h3><p>在 Schedule Trigger 後加 <strong>RSS Read</strong> 節點，填入 URL：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://www.theverge.com/rss/ai-artificial-intelligence/index.xml</span><br></pre></td></tr></table></figure><h3 id="步驟三：Filter-節點過濾日期"><a href="#步驟三：Filter-節點過濾日期" class="headerlink" title="步驟三：Filter 節點過濾日期"></a>步驟三：Filter 節點過濾日期</h3><p>在 RSS Read 後加 <strong>Filter</strong> 節點，條件：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">isoDate → is after → &#123;&#123; $now.minus(&#123;days: 7&#125;).toISO() &#125;&#125;</span><br></pre></td></tr></table></figure><h3 id="步驟四：Edit-Fields-節點標準化輸出"><a href="#步驟四：Edit-Fields-節點標準化輸出" class="headerlink" title="步驟四：Edit Fields 節點標準化輸出"></a>步驟四：Edit Fields 節點標準化輸出</h3><p>在 Filter 後加 <strong>Edit Fields（Set）</strong> 節點，對應欄位：</p><table><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><tr><td><code>title</code></td><td><code>&#123;&#123; $json.title &#125;&#125;</code></td></tr><tr><td><code>url</code></td><td><code>&#123;&#123; $json.link &#125;&#125;</code></td></tr><tr><td><code>content</code></td><td><code>&#123;&#123; $json.contentSnippet &#125;&#125;</code></td></tr><tr><td><code>published_at</code></td><td><code>&#123;&#123; $json.isoDate &#125;&#125;</code></td></tr><tr><td><code>source</code></td><td><code>The Verge AI</code></td></tr></tbody></table><p>執行成功，每筆輸出格式如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Cloud development platform Vercel was hacked&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://www.theverge.com/tech/914723/vercel-hacked&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Vercel, a major development platform...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;published_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-04-19T19:54:52.000Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;The Verge AI&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>四個步驟完成，收集層跑通。每次執行會輸出統一格式的文章列表，準備交給處理層處理。</p><hr><h2 id="API-Credential-的資安決策"><a href="#API-Credential-的資安決策" class="headerlink" title="API Credential 的資安決策"></a>API Credential 的資安決策</h2><p>設定 Google Gemini credential 時，有一個值得討論的選項：<strong>Allowed HTTP Request Domains</strong>。</p><p>n8n 官方說明：</p><blockquote><p>Control which domains this credential can be used with in HTTP Request nodes</p></blockquote><p>這個設定限制的是<strong>被呼叫端</strong>（destination）——也就是「這組 key 只能被送往哪些 domain」，不是「誰能在 n8n 裡使用這組 key」。</p><p><strong>能防護的範圍</strong>：</p><ul><li>攻擊者透過 HTTP Request 節點把 key 帶著打到外部惡意 server</li></ul><p><strong>防護不到的範圍</strong>：</p><ul><li>n8n 原生節點（如 Google Gemini Chat Model）</li><li>直接存取 n8n 資料庫</li><li>從 n8n UI 讀取 credential</li></ul><p><strong>決策</strong>：填 <code>generativelanguage.googleapis.com</code>，遵守最小權限原則。但主要防線是 Cloudflare Access（限制誰能登入 n8n），這個設定是額外的縱深防禦，不能過度依賴。</p><p>核心原則：<strong>理解每層保護的邊界，不要以為設了就完全安全。</strong></p><hr><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/n8n-1-in-gcp-free-tier-vm/">個人自動化平台(一) n8n &amp; GCP VM</a></li><li><a href="/2026/n8n-2-cloudflared-and-tunnel/">個人自動化平台(二) Cloudflare Access &amp; Cloudflare Tunnel</a></li><li><a href="/2026/n8n-3-pipeline-overview/">個人自動化平台(三) 收集、處理、輸出：三層可插拔管道設計</a></li><li><a href="/2026/n8n-3.1-rebuild-infromation/">個人自動化平台(番外) 拆掉重建 GCP VM &amp; Cloudflared Tunnel &amp; Cloudflare Access</a></li><li><a href="/2026/n8n-5-pipeline-process/">個人自動化平台(五) n8n 實作：處理層，Aggregate + Gemini 週報整理</a></li><li><a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六) n8n 實作：輸出層，Instagram API 取得 Token 全記錄</a></li></ul><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>收集層用 RSS Read + Filter 過濾近 7 天，四個來源統一輸出格式</li><li>Credential 的 Allowed HTTP Request Domains 是縱深防禦，主防線還是 Cloudflare Access</li><li>RSS Read 是「簡單優先」的選擇，之後有需要再加暫存層優化</li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/20/2026/n8n-4-pipeline-fetch/</id>
    <link href="https://blog.marsen.me/2026/04/20/2026/n8n-4-pipeline-fetch/"/>
    <published>2026-04-20T10:15:17.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>上一篇定好了「收集、處理、輸出」三層架構，這篇開始實作第一層：收集。</p>
<p>目標是從四個 AI 新聞來源拉資料，輸出]]>
    </summary>
    <title>[實作筆記] 個人自動化平台(四) n8n 實作：收集層，RSS 資料來源</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
  <entry>
    <author>
      <name>Marsen L.</name>
      <email>admin@marsen.me</email>
    </author>
    <category term="實作筆記" scheme="https://blog.marsen.me/tags/%E5%AF%A6%E4%BD%9C%E7%AD%86%E8%A8%98/"/>
    <content>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>前兩篇把 GCP VM + n8n + cloudflared tunnel 都設好了，環境就緒。</p><p>這篇不急著動手，先把架構想清楚。</p><p>目標是一個可以自由擴充的個人內容自動化平台：新增資料來源、換 AI 模型、改輸出管道，都不需要重寫整個流程。</p><hr><h2 id="核心設計：三層分離"><a href="#核心設計：三層分離" class="headerlink" title="核心設計：三層分離"></a>核心設計：三層分離</h2><p>把整個管道拆成三層，每層只負責一件事：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">收集（Fetch）   →   處理（Process）   →   輸出（Sink）</span><br></pre></td></tr></table></figure><ul><li><strong>收集</strong>：從外部拿資料，不做任何處理</li><li><strong>處理</strong>：理解資料，產生新資訊</li><li><strong>輸出</strong>：把結果推出去</li></ul><p>每層都是介面，實作可以自由替換或組合。</p><hr><h2 id="每層的邊界"><a href="#每層的邊界" class="headerlink" title="每層的邊界"></a>每層的邊界</h2><h3 id="收集（Fetch）"><a href="#收集（Fetch）" class="headerlink" title="收集（Fetch）"></a>收集（Fetch）</h3><p>負責從各種來源拉資料：RSS、API、Webhook、爬蟲……</p><p>不管來源是什麼，輸出格式統一：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文章標題&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;原文內容或摘要&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;來源名稱&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;published_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-04-20T00:00:00Z&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>這個格式就是「收集」和「處理」之間的契約。只要每個來源都輸出這個 schema，「處理」完全不需要知道資料從哪來。</p><p>新增來源：建一個新的 sub-workflow，輸出同樣格式。<br>停用來源：把那個 sub-workflow 關掉。</p><h3 id="處理（Process）"><a href="#處理（Process）" class="headerlink" title="處理（Process）"></a>處理（Process）</h3><p>負責理解資料，產生新資訊。</p><p>目前規劃的功能：</p><table><thead><tr><th>功能</th><th>說明</th></tr></thead><tbody><tr><td>摘要歸納</td><td>把長文濃縮成重點</td></tr><tr><td>事實查核</td><td>驗證內容可信度（未來）</td></tr><tr><td>創意發想</td><td>從資料延伸新觀點（未來）</td></tr></tbody></table><p>AI 是「處理」層的實作細節，不是介面的一部分。</p><p>今天用 Claude，明天換 Gemini，或跑地端模型（Ollama）——主流程不在意，只呼叫「處理」的 sub-workflow，不管裡面用的是什麼模型。</p><p>「處理」層的輸出格式：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文章標題&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;summary&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AI 整理的重點&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;tags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;AI&quot;</span><span class="punctuation">,</span> <span class="string">&quot;LLM&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;來源名稱&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;published_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-04-20T00:00:00Z&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="輸出（Sink）"><a href="#輸出（Sink）" class="headerlink" title="輸出（Sink）"></a>輸出（Sink）</h3><p>負責把結果推出去。</p><p>兩個維度都可以擴充：</p><p><strong>格式</strong>：</p><ul><li>純文字</li><li>簡報（未來）</li><li>影音（未來）</li></ul><p><strong>管道</strong>：</p><ul><li>Email</li><li>LINE</li><li>YouTube &#x2F; Instagram &#x2F; Facebook（未來）</li></ul><p>從「處理」到「輸出」可以有多條路線，同一批摘要可以同時走不同路線，互不干擾：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">處理（摘要結果）</span><br><span class="line">  ├─ → Email 週報（長版純文字）</span><br><span class="line">  ├─ → LINE 推播（短版）</span><br><span class="line">  └─ → 存檔（JSON，備用）</span><br></pre></td></tr></table></figure><hr><h2 id="在-n8n-的實作方式"><a href="#在-n8n-的實作方式" class="headerlink" title="在 n8n 的實作方式"></a>在 n8n 的實作方式</h2><p>n8n 的 sub-workflow 機制很適合做這件事：</p><ul><li>每個「收集」來源 → 一個 sub-workflow</li><li>每個「處理」功能 → 一個 sub-workflow</li><li>每個「輸出」管道 → 一個 sub-workflow</li><li>主流程：用 <strong>Execute Workflow</strong> 節點串起來，用 <strong>Merge</strong> 合併多個來源，用 <strong>Switch &#x2F; If</strong> 分流到不同輸出</li></ul><p>新增或停用任何環節，只需要在主流程加減節點，不動其他邏輯。</p><hr><h2 id="小結"><a href="#小結" class="headerlink" title="小結"></a>小結</h2><ul><li>三層分離（收集、處理、輸出），每層依賴介面不依賴實作</li><li>各層之間用固定的 JSON schema 溝通，是可替換性的基礎</li><li>「處理」層的 AI 模型是實作細節，抽成 sub-workflow 就能自由替換</li><li>「輸出」層支援多路線同時輸出，格式和管道各自獨立擴充</li><li>n8n 的 sub-workflow 機制天然適合這個設計</li></ul><p>下一篇開始動手實作第一條 pipeline：AI 新聞週報。</p><h2 id="參考"><a href="#參考" class="headerlink" title="參考"></a>參考</h2><ul><li><a href="/2026/n8n-1-in-gcp-free-tier-vm/">個人自動化平台(一) n8n &amp; GCP VM</a></li><li><a href="/2026/n8n-2-cloudflared-and-tunnel/">個人自動化平台(二) Cloudflare Access &amp; Cloudflare Tunnel</a></li><li><a href="/2026/n8n-3.1-rebuild-infromation/">個人自動化平台(番外) 拆掉重建 GCP VM &amp; Cloudflared Tunnel &amp; Cloudflare Access</a></li><li><a href="/2026/n8n-4-pipeline-fetch/">個人自動化平台(四) n8n 實作：收集層，RSS 資料來源</a></li><li><a href="/2026/n8n-5-pipeline-process/">個人自動化平台(五) n8n 實作：處理層，Aggregate + Gemini 週報整理</a></li><li><a href="/2026/n8n-6-pipeline-output/">個人自動化平台(六) n8n 實作：輸出層，Instagram API 取得 Token 全記錄</a></li></ul><p>(fin)</p>]]>
    </content>
    <id>https://blog.marsen.me/2026/04/20/2026/n8n-3-pipeline-overview/</id>
    <link href="https://blog.marsen.me/2026/04/20/2026/n8n-3-pipeline-overview/"/>
    <published>2026-04-20T06:20:23.000Z</published>
    <summary>
      <![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>前兩篇把 GCP VM + n8n + cloudflared tunnel 都設好了，環境就緒。</p>
<p>這篇不急著]]>
    </summary>
    <title>[實作筆記] 個人自動化平台(三) 收集、處理、輸出：三層可插拔管道設計</title>
    <updated>2026-06-12T11:37:08.401Z</updated>
  </entry>
</feed>
