{
    "version": "https://jsonfeed.org/version/1",
    "title": "Keep Flourishing",
    "description": "Young's LIFE",
    "home_page_url": "https://akebee.com",
    "items": [
        {
            "id": "https://akebee.com/voip-sdp-g711-codec-mismatch/",
            "url": "https://akebee.com/voip-sdp-g711-codec-mismatch/",
            "title": "SIP Protocol 教學（二）：為什麼 VoIP 通話聲音失真？從 SDP 協商到 G.711 編碼一次搞懂",
            "date_published": "2026-03-09T16:00:00.000Z",
            "content_html": "<blockquote>\n<p>語音開發這塊領域的術語確實很硬，因為它跨接了通訊協定和音訊工程。這篇文章把整個問題鏈拆解成白話文，帶你從根本理解：一個「聲音失真」的 Bug 背後，藏了多少層細節。</p>\n</blockquote>\n<h2 id=\"1-什麼是-sdp談合約\"><a class=\"anchor\" href=\"#1-什麼是-sdp談合約\">#</a> 1. 什麼是 SDP？（談合約）</h2>\n<p><strong>SDP（Session Description Protocol，會話描述協定）</strong> 就像是兩支電話要接通前的「<strong>規格合約書</strong>」。</p>\n<p>根據 <a href=\"https://datatracker.ietf.org/doc/html/rfc4566\">RFC 4566</a> 的定義，SDP 本身不傳輸任何媒體，它只負責在兩端之間協商：</p>\n<ul>\n<li>支援哪些編碼格式（Codec）</li>\n<li>使用哪個 IP/Port 接收音訊</li>\n<li>取樣率、頻道數等媒體參數</li>\n</ul>\n<p>實際流程依照 <a href=\"https://www.rfc-editor.org/rfc/rfc3264\">RFC 3264</a> 定義的 <strong>Offer/Answer 模型</strong>：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>手機（Offerer）          FreeSWITCH（Answerer）</span></span>\n<span class=\"line\"><span>      |                        |</span></span>\n<span class=\"line\"><span>      | ── SDP Offer ────────> |  「我支援 PCMU(0) 和 PCMA(8)」</span></span>\n<span class=\"line\"><span>      |                        |</span></span>\n<span class=\"line\"><span>      | &#x3C;── SDP Answer ─────── |  「好，我選 PCMU(0)」</span></span>\n<span class=\"line\"><span>      |                        |</span></span>\n<span class=\"line\"><span>      |  ═══ 語音串流 (RTP) ══ |  雙方用同一個 Codec 通話</span></span></code></pre>\n<blockquote>\n<p><strong>合約類比：</strong> 手機開出條件清單，後端選一個接受，通話才正式開始。</p>\n</blockquote>\n<hr />\n<h2 id=\"2-pcmu-vs-pcma同家族不同語言\"><a class=\"anchor\" href=\"#2-pcmu-vs-pcma同家族不同語言\">#</a> 2. PCMU vs. PCMA：同家族，不同語言</h2>\n<p>PCMU 和 PCMA 都是 <strong>G.711</strong> 標準的成員（ITU-T Recommendation G.711），但編碼演算法完全不同：</p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th><strong>PCMU（μ-law）</strong></th>\n<th><strong>PCMA（A-law）</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>SDP Payload Type</strong></td>\n<td><code>0</code></td>\n<td><code>8</code></td>\n</tr>\n<tr>\n<td><strong>主要使用地區</strong></td>\n<td>北美、日本、台灣</td>\n<td>歐洲、部分亞洲</td>\n</tr>\n<tr>\n<td><strong>壓縮曲線</strong></td>\n<td>μ-law 對數壓縮</td>\n<td>A-law 對數壓縮</td>\n</tr>\n<tr>\n<td><strong>取樣率</strong></td>\n<td>8000 Hz</td>\n<td>8000 Hz</td>\n</tr>\n<tr>\n<td><strong>位元深度</strong></td>\n<td>8-bit</td>\n<td>8-bit</td>\n</tr>\n</tbody>\n</table>\n<p>兩者的 <strong>壓縮轉換表（量化映射）完全不同</strong>。同樣一個 8-bit 數值，用 μ-law 解出來是 A，用 A-law 解出來是 B——差異可以高達幾倍的振幅。</p>\n<hr />\n<h2 id=\"3-實戰一個-bug-的解剖\"><a class=\"anchor\" href=\"#3-實戰一個-bug-的解剖\">#</a> 3. 實戰：一個 Bug 的解剖</h2>\n<h3 id=\"根本原因codec-mismatch編碼不匹配\"><a class=\"anchor\" href=\"#根本原因codec-mismatch編碼不匹配\">#</a> 根本原因：Codec Mismatch（編碼不匹配）</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>SDP 提供：  PCMU (0) + PCMA (8)</span></span>\n<span class=\"line\"><span>手機選擇：  PCMA（A-law）</span></span>\n<span class=\"line\"><span>後端解碼：  永遠用 μ-law 硬解</span></span>\n<span class=\"line\"><span>結果：      A-law 資料被當成 μ-law 解 → 完全錯誤的 PCM → 機器雜音 / 爆音</span></span></code></pre>\n<p>這就像把德文稿子交給只看英文的翻譯機——讀出來的「意思」完全亂掉。映射錯誤導致波形振幅異常噴發，聽起來就是刺耳的雜音加上音量爆炸。</p>\n<h3 id=\"修復方式強制-sdp-只提供-pcmu\"><a class=\"anchor\" href=\"#修復方式強制-sdp-只提供-pcmu\">#</a> 修復方式：強制 SDP 只提供 PCMU</h3>\n<p>在 <code>answer_service.go</code> 修改 SDP 回應，只列出 PCMU：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>// Before（提供兩種 Codec，讓對方選）</span></span>\n<span class=\"line\"><span>m=audio 10000 RTP/AVP 0 8</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>// After（只提供 PCMU，對方沒得選）</span></span>\n<span class=\"line\"><span>m=audio 10000 RTP/AVP 0</span></span></code></pre>\n<p>手機端收到 Answer 後，<strong>只能用 PCMU 發送</strong>。後端解碼對了，聲音立刻清晰。</p>\n<hr />\n<h2 id=\"4-振幅amplitude與聲音大小\"><a class=\"anchor\" href=\"#4-振幅amplitude與聲音大小\">#</a> 4. 振幅（Amplitude）與聲音大小</h2>\n<p>修好之後你可能會發現：<strong>聲音變小了</strong>。</p>\n<p>這其實是「正常」。之前的「大聲」是解碼錯誤造成的異常振幅（雜訊）；現在的 PCMU 正確解碼後，原始語音的振幅才是它本來的大小。</p>\n<blockquote>\n<p><strong>振幅（Amplitude）：</strong> 音訊波形的高度，直接對應聲音大小。高度越高，能量越強，聲音越大。</p>\n</blockquote>\n<hr />\n<h2 id=\"5-gain-與-audioprocessor給-ai-裝擴大機\"><a class=\"anchor\" href=\"#5-gain-與-audioprocessor給-ai-裝擴大機\">#</a> 5. Gain 與 AudioProcessor：給 AI 裝擴大機</h2>\n<p>ASR（自動語音辨識）就像人耳，<strong>聲音太小會聽不清楚</strong>，辨識準確率會下降。</p>\n<p>解法：在輸入 ASR 前加一層音訊處理元件 <code>VolumeAdjustedAudioProcessor</code>，對每個音訊 frame 做增益放大：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>輸入音訊 → VolumeAdjustedAudioProcessor (gain: 3.0) → ASR Engine</span></span>\n<span class=\"line\"><span>              └─ 每個 sample × 3.0 → 振幅放大 3 倍</span></span></code></pre>\n<p><strong>Gain（增益）</strong> 就是那個倍數：</p>\n<ul>\n<li><code>1.0</code> = 原始音量（不變）</li>\n<li><code>2.0</code> = 音量翻倍</li>\n<li><code>3.0 ~ 4.0</code> = 進一步放大，提升 ASR 辨識率</li>\n</ul>\n<blockquote>\n<p>⚠️ Gain 不能無限調高，過大會讓波形「削頂（clipping）」，反而產生新的失真。</p>\n</blockquote>\n<hr />\n<h2 id=\"6-整理你解決了什麼\"><a class=\"anchor\" href=\"#6-整理你解決了什麼\">#</a> 6. 整理：你解決了什麼</h2>\n<table>\n<thead>\n<tr>\n<th>名詞</th>\n<th>白話解釋</th>\n<th>結果</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>SDP</strong></td>\n<td>通話前的規格合約</td>\n<td>已修正，強制統一 PCMU</td>\n</tr>\n<tr>\n<td><strong>PCMU</strong></td>\n<td>G.711 μ-law，北美/台灣標準編碼</td>\n<td>現在唯一使用的 Codec</td>\n</tr>\n<tr>\n<td><strong>Codec Mismatch</strong></td>\n<td>說不同語言導致的解碼錯誤</td>\n<td>已解決，根本原因修除</td>\n</tr>\n<tr>\n<td><strong>失真 / 爆音</strong></td>\n<td>解碼錯誤造成的波形異常</td>\n<td>已消除</td>\n</tr>\n<tr>\n<td><strong>Gain</strong></td>\n<td>音量放大倍率</td>\n<td>從 2.0 調高，提升 ASR 辨識率</td>\n</tr>\n</tbody>\n</table>\n<hr />\n<h2 id=\"延伸閱讀\"><a class=\"anchor\" href=\"#延伸閱讀\">#</a> 延伸閱讀</h2>\n<ul>\n<li><a href=\"https://datatracker.ietf.org/doc/html/rfc4566\">RFC 4566 – SDP: Session Description Protocol</a></li>\n<li><a href=\"https://www.rfc-editor.org/rfc/rfc3264\">RFC 3264 – An Offer/Answer Model with SDP</a></li>\n<li><a href=\"https://www.rfc-editor.org/rfc/rfc4317.html\">RFC 4317 – SDP Offer/Answer Examples</a></li>\n<li><a href=\"https://www.itu.int/rec/T-REC-G.711\">ITU-T G.711 – Pulse Code Modulation of Voice Frequencies</a></li>\n<li><a href=\"https://en.wikipedia.org/wiki/G.711\">Wikipedia: G.711</a></li>\n</ul>\n",
            "tags": [
                "VoIP",
                "SIP",
                "SDP",
                "G.711",
                "FreeSWITCH",
                "音訊工程",
                "ASR",
                "Go"
            ]
        },
        {
            "id": "https://akebee.com/golang-extension/",
            "url": "https://akebee.com/golang-extension/",
            "title": "Go 語言函式定義跳轉 (VS Code)",
            "date_published": "2025-12-26T08:10:04.000Z",
            "content_html": "<h2 id=\"過程\"><a class=\"anchor\" href=\"#過程\">#</a> 過程</h2>\n<p>開發時，已經習慣長按 ctrl+類名或函式名稱，讓編輯器自動跳轉到該類別或函式的定義位置來快速查看程式碼的實作細節</p>\n<p><img loading=\"lazy\" src=\"/images/2025/golang-extension/press-ctrl.png\" alt=\"golang jump\" /></p>\n<p>由於我改用 mise 來管理 golang 版本，導致 vscode golang extension 無法正確辨識 golang 的執行檔位置，長按 ctrl + 點擊類名或函式名稱時就沒反應</p>\n<h2 id=\"解法\"><a class=\"anchor\" href=\"#解法\">#</a> 解法</h2>\n<p><code>ctrl + ,</code> 快速打開設定頁面，搜尋 <code>go.alternateTools</code> 並點擊在 settings.json 中編輯</p>\n<p><img loading=\"lazy\" src=\"/images/2025/golang-extension/alternate-tool.png\" alt=\"alter tools\" /></p>\n<p>直接在 settings.json 中加入以下設定</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-json\"><span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">....</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">go.alternateTools</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">: </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#99841877;--shiki-dark:#B8A96577\">    \"</span><span style=\"color:#998418;--shiki-dark:#B8A965\">go</span><span style=\"color:#99841877;--shiki-dark:#B8A96577\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/Users/young/.local/share/mise/shims/go</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span></code></pre>\n<p>在 reload window 後，重新長按 ctrl + 點擊類名或函式名稱，就可以順利跳轉到該定義位置了</p>\n",
            "tags": [
                "Golang"
            ]
        },
        {
            "id": "https://akebee.com/process-thread-coroutine/",
            "url": "https://akebee.com/process-thread-coroutine/",
            "title": "Process, Thread, Coroutine，A Hands-on Experiment to Conquer Concurrency in Python and Golang",
            "date_published": "2025-12-12T08:52:26.000Z",
            "content_html": "<blockquote>\n<p><strong>引言：</strong> 為了真正理解現代程式設計中的 Concurrency（併發）概念，稍微複習了 Process, Thread, Coroutine 之間的關係，並透過程式碼徹底比較 Python 與 Golang 在處理 CPU-bound 和 I/O-bound 任務時的效能差異。結果非常驚人，清晰地揭示了 Python GIL 的限制與 Golang Runtime 的優勢。</p>\n</blockquote>\n<h2 id=\"process-thread-coroutine\"><a class=\"anchor\" href=\"#process-thread-coroutine\">#</a> Process, Thread, Coroutine</h2>\n<p>從資源開銷和獨立性的角度來看，併發執行單元的層次結構由大到小排序如下：</p>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">概念</th>\n<th style=\"text-align:left\">資源開銷 / 獨立性</th>\n<th style=\"text-align:left\">核心特性</th>\n<th style=\"text-align:left\">適用場景</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\"><strong>External Service / Daemon</strong></td>\n<td style=\"text-align:left\"><strong>系統級最大</strong> (完全獨立)</td>\n<td style=\"text-align:left\">完全獨立的執行環境，透過網路或 IPC 進行通訊。</td>\n<td style=\"text-align:left\"><strong>微服務架構</strong>、背景常駐服務、系統資源隔離。</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Process</strong> (行程/進程)</td>\n<td style=\"text-align:left\"><strong>最大</strong>（獨立記憶體）</td>\n<td style=\"text-align:left\">OS 調度，<strong>可繞過 GIL</strong> 實現 Parallelism (並行)。</td>\n<td style=\"text-align:left\"><strong>CPU-Bound 任務</strong></td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Thread</strong> (執行緒/線程)</td>\n<td style=\"text-align:left\"><strong>較小</strong>（共享記憶體）</td>\n<td style=\"text-align:left\">OS 調度，受 Python GIL 限制，<strong>無法並行運算</strong>。</td>\n<td style=\"text-align:left\"><strong>I/O-Bound 任務</strong> (等待時釋放 GIL)</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Coroutine</strong> (協程/Goroutine)</td>\n<td style=\"text-align:left\"><strong>最小</strong>（單 Thread 內切換）</td>\n<td style=\"text-align:left\">程式控制，開銷極低，實現 Non-blocking (非阻塞) Concurrency。</td>\n<td style=\"text-align:left\"><strong>高併發 I/O</strong></td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"cpu-bound-vs-io-bound\"><a class=\"anchor\" href=\"#cpu-bound-vs-io-bound\">#</a> CPU-bound vs I/O-bound</h2>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">特性</th>\n<th style=\"text-align:left\">CPU-bound (CPU 密集型)</th>\n<th style=\"text-align:left\">I/O-bound (I/O 密集型)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\"><strong>主要工作</strong></td>\n<td style=\"text-align:left\">計算、邏輯處理</td>\n<td style=\"text-align:left\">等待外部資源 (硬碟/網路)</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>效能瓶頸</strong></td>\n<td style=\"text-align:left\">CPU 速度、核心數量</td>\n<td style=\"text-align:left\">I/O 設備速度、網路延遲</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>CPU 使用率</strong></td>\n<td style=\"text-align:left\">高 (接近 100%)</td>\n<td style=\"text-align:left\">低 (CPU 經常閒置)</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>優化策略</strong></td>\n<td style=\"text-align:left\">多行程 (Multi-processing)</td>\n<td style=\"text-align:left\">多執行緒 (Multi-threading) 或 非同步 I/O</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Python 考量</strong></td>\n<td style=\"text-align:left\">受到 <strong>GIL</strong> 限制，需用多行程</td>\n<td style=\"text-align:left\"><strong>GIL</strong> 在 I/O 等待時釋放，適合多執行緒/非同步</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"python-vs-golang-的-process-thread-coroutine-實驗\"><a class=\"anchor\" href=\"#python-vs-golang-的-process-thread-coroutine-實驗\">#</a> Python vs Golang 的 Process, Thread, Coroutine 實驗</h2>\n<p>本次測試透過兩種典型的工作負載，看看 Python GIL 與 Golang 的高效能併發機制對比</p>\n<ul>\n<li>\n<p><strong>情景 A：CPU-Bound 任務</strong> (<code>cpu_bound_task</code>):</p>\n<ul>\n<li><strong>邏輯：</strong> 執行 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><msub><mi>N</mi><mrow><mi>C</mi><mi>P</mi><mi>U</mi></mrow></msub><mo>×</mo><mn>500</mn><mo separator=\"true\">,</mo><mn>000</mn></mrow><annotation encoding=\"application/x-tex\">N_{CPU} \\times 500,000</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\" style=\"margin-right:0.10903em;\">N</span><span class=\"msupsub\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.3283em;\"><span style=\"top:-2.55em;margin-left:-0.109em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\"><span class=\"mord mathnormal mtight\" style=\"margin-right:0.13889em;\">CP</span><span class=\"mord mathnormal mtight\" style=\"margin-right:0.10903em;\">U</span></span></span></span></span><span class=\"vlist-s\">​</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.15em;\"><span></span></span></span></span></span></span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span><span class=\"mbin\">×</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8389em;vertical-align:-0.1944em;\"></span><span class=\"mord\">500</span><span class=\"mpunct\">,</span><span class=\"mspace\" style=\"margin-right:0.1667em;\"></span><span class=\"mord\">000</span></span></span></span> 次數學運算。</li>\n<li><strong>目的：</strong> 使 CPU 核心持續保持 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mn>100</mn><mi mathvariant=\"normal\">%</mi></mrow><annotation encoding=\"application/x-tex\">100\\%</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.8056em;vertical-align:-0.0556em;\"></span><span class=\"mord\">100%</span></span></span></span> 負載。測試機制能否將工作<strong>並行 (Parallel)</strong> 分散到多核上。</li>\n</ul>\n</li>\n<li>\n<p><strong>情景 B：I/O-Bound 任務</strong> (<code>io_bound_task</code> / <code>async_io_bound_task</code>):</p>\n<ul>\n<li><strong>邏輯：</strong> 使用 <code>time.sleep(DELAY_IO)</code> 模擬網路延遲或檔案讀寫等待（<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mn>0.5</mn></mrow><annotation encoding=\"application/x-tex\">0.5</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.5</span></span></span></span> 秒）。</li>\n<li><strong>目的：</strong> 使 CPU 處於<strong>空閒等待</strong>狀態。測試機制能否在等待時<strong>高效地切換</strong>到其他任務，避免整體阻塞。</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"2-測試目的與邏輯8-倍速的挑戰\"><a class=\"anchor\" href=\"#2-測試目的與邏輯8-倍速的挑戰\">#</a> 2. 測試目的與邏輯：8 倍速的挑戰</h3>\n<p>設置了 <strong>8 個並發任務 (<span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><msub><mi>N</mi><mrow><mi>W</mi><mi>O</mi><mi>R</mi><mi>K</mi><mi>E</mi><mi>R</mi><mi>S</mi></mrow></msub><mo>=</mo><mn>8</mn></mrow><annotation encoding=\"application/x-tex\">N_{WORKERS}=8</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.8333em;vertical-align:-0.15em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\" style=\"margin-right:0.10903em;\">N</span><span class=\"msupsub\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.3283em;\"><span style=\"top:-2.55em;margin-left:-0.109em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\"><span class=\"mord mathnormal mtight\" style=\"margin-right:0.13889em;\">W</span><span class=\"mord mathnormal mtight\" style=\"margin-right:0.00773em;\">OR</span><span class=\"mord mathnormal mtight\" style=\"margin-right:0.07153em;\">K</span><span class=\"mord mathnormal mtight\" style=\"margin-right:0.05764em;\">ERS</span></span></span></span></span><span class=\"vlist-s\">​</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.15em;\"><span></span></span></span></span></span></span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">8</span></span></span></span>)</strong>，並將其以三種不同的方式運行：</p>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">測試機制</th>\n<th style=\"text-align:left\">邏輯目標</th>\n<th style=\"text-align:left\">測試目的</th>\n<th style=\"text-align:left\"><strong>預期結果</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\"><strong>Process</strong></td>\n<td style=\"text-align:left\"><strong>CPU-Bound</strong></td>\n<td style=\"text-align:left\"><strong>驗證並行：</strong> 能否利用 8 個核心。</td>\n<td style=\"text-align:left\">總時間 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mn>0.1</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">\\approx 0.1\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.1</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span> (單核時間 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>÷</mo><mn>8</mn></mrow><annotation encoding=\"application/x-tex\">\\div 8</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.7278em;vertical-align:-0.0833em;\"></span><span class=\"mord\">÷</span><span class=\"mord\">8</span></span></span></span>)</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Thread / Asyncio</strong></td>\n<td style=\"text-align:left\"><strong>CPU-Bound</strong></td>\n<td style=\"text-align:left\"><strong>驗證 GIL 限制：</strong> 是否會因 GIL 而變成串行。</td>\n<td style=\"text-align:left\">總時間 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mn>0.4</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">\\approx 0.4\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.4</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span> (所有任務時間相加)</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Asyncio / Goroutine</strong></td>\n<td style=\"text-align:left\"><strong>I/O-Bound</strong></td>\n<td style=\"text-align:left\"><strong>驗證切換效率：</strong> 能否在 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mn>0.5</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">0.5\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.5</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span> 內完成 8 個任務。</td>\n<td style=\"text-align:left\">總時間 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mrow><mn mathvariant=\"bold\">0.5</mn><mtext>s</mtext></mrow></mrow><annotation encoding=\"application/x-tex\">\\approx \\mathbf{0.5\\text{s}}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\"><span class=\"mord mathbf\">0.5</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></span> (最長任務時間)</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"3-公平測試\"><a class=\"anchor\" href=\"#3-公平測試\">#</a> 3. 公平測試</h3>\n<ul>\n<li><strong>Python <code>multiprocessing.Process</code>:</strong> 啟動獨立行程，這是 Python <strong>唯一</strong>能繞過 GIL 實現 <strong>CPU 並行</strong>的方法。</li>\n<li><strong>Python <code>asyncio.to_thread</code>:</strong> 處理 Asyncio CPU 任務的標準做法，結果將揭示其後台執行緒池仍受 <strong>GIL 限制</strong>。</li>\n<li><strong>Golang Goroutine:</strong> Go 語言的核心併發單元，由 Runtime M:N 排程器管理，測試其在兩種情景下的全能性。</li>\n</ul>\n<p>通過比較 <strong>Process (並行)</strong> 與 <strong>Thread/Asyncio (串行)</strong> 在 CPU 任務上的時間差異，可直接測量 <strong>GIL 導致的效能差距</strong>。</p>\n<h3 id=\"python\"><a class=\"anchor\" href=\"#python\">#</a> Python</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> os</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> multiprocessing </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Process</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> threading </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Thread</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> asyncio</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># --- 參數設定 ---</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 測試執行緒/行程/協程的數量</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">NUM_WORKERS</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 8</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># CPU 密集型任務的強度 (數值越大，運算時間越久，請根據您的電腦性能調整)</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">N_CPU</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 2</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># I/O 密集型任務的模擬等待時間</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">DELAY_IO</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.5</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># --- 任務定義 ---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 1. CPU 密集型任務：耗時的計算</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> cpu_bound_task</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">n</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">佔用 CPU 的運算任務</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    i </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> n </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">*</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 500000</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        i </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # 進行簡單的數學運算以佔用 CPU</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        x </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 51000</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">**</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0.5</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"[</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">os</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">getpid</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">] CPU-Bound Task finished.\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 2. I/O 密集型任務：模擬網路等待</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> io_bound_task</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">delay</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">模擬網路請求，等待指定時間</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">sleep</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">delay</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"[</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">os</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">getpid</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">] I/O-Bound Task finished after </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">delay</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s.\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 3. I/O 密集型任務的協程版本</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">async</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> async_io_bound_task</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">delay</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">模擬網路請求，使用 await 非阻塞等待</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    await</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> asyncio</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">sleep</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">delay</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"[Asyncio] I/O-Bound Task finished after </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">delay</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s.\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 4. CPU 密集型任務的協程版本 (修復：使用 to_thread 將阻塞任務拋給執行緒池)</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">async</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> async_cpu_bound_task</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">n</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">將 CPU 運算拋給後台執行緒池，避免阻塞事件循環</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 使用 asyncio.to_thread 將同步函數 cpu_bound_task 在單獨的執行緒中運行</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 這是 Asyncio 處理 CPU 密集型任務的標準做法。</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    await</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> asyncio</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">to_thread</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">cpu_bound_task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> n</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"[Asyncio] CPU-Bound Task finished.\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># --- 運行函數 ---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># A. 行程 (Process) 執行</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> run_processes</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">args</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    start_time </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    processes </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> _ </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">NUM_WORKERS</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        p </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Process</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">target</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> args</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">args</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        processes</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">p</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        p</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">start</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> p </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> processes</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        p</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">join</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    end_time </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> end_time </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> start_time</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># B. 執行緒 (Thread) 執行</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> run_threads</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">args</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    start_time </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    threads </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> _ </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">NUM_WORKERS</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        t </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Thread</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">target</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> args</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">args</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        threads</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">t</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        t</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">start</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> t </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> threads</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        t</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">join</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    end_time </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> end_time </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> start_time</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># C. 協程 (Asyncio) 執行</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> run_asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">args</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    start_time </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 1. 創建協程列表 (它們是協程對象)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    tasks </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">task</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">*</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">args</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> _ </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">NUM_WORKERS</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 2. 定義一個頂層協程，用來等待所有任務完成</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    async</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> main_runner</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # await 必須在 async 函數內，用於等待 Future 完成</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        await</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> asyncio</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">gather</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">*</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">tasks</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 3. 運行事件循環，傳遞頂層協程 (main_runner() 返回 Coroutine Object)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    asyncio</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">run</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">main_runner</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    end_time </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> end_time </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> start_time</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># --- 主程式執行區 ---</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">if</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> __name__</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">__main__</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"--- Python 併發機制測試 (N=</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">NUM_WORKERS</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">, CPU_N=</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">N_CPU</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">, IO_D=</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">DELAY_IO</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">) ---\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 1. CPU 密集型任務比較 (應比較 Process vs Thread vs Asyncio)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[--- CPU 密集型測試 (計算運算) ---]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 【Python 1】Process：**最適用**，應最快，因繞過 GIL</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    time_p_cpu </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> run_processes</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">cpu_bound_task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> N_CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Process (CPU) Total Time: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time_p_cpu</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">:.4f</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 【Python 2】Thread：**不適用**，應最慢，因受 GIL 限制</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    time_t_cpu </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> run_threads</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">cpu_bound_task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> N_CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Thread (CPU) Total Time: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time_t_cpu</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">:.4f</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 【Python 3】Asyncio：**不適用**，應與 Thread 接近，因單執行緒阻塞</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    time_a_cpu </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> run_asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">async_cpu_bound_task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> N_CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Asyncio (CPU) Total Time: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time_a_cpu</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">:.4f</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 2. I/O 密集型任務比較 (應比較 Thread vs Asyncio)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[--- I/O 密集型測試 (網路等待) ---]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 【Python 4】Thread：適用，因 I/O 等待時能釋放 GIL</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    time_t_io </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> run_threads</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">io_bound_task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> DELAY_IO</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Thread (I/O) Total Time: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time_t_io</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">:.4f</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 【Python 5】Asyncio：**最適用**，應最快，因切換開銷最小</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    time_a_io </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> run_asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">async_io_bound_task</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> DELAY_IO</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Asyncio (I/O) Total Time: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">time_a_io</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">:.4f</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # Process 處理 I/O 任務通常開銷較大，一般不使用，故不列入主要比較。</span></span></code></pre>\n<p>輸出結果：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">---</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Python</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 併發機制測試</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">N=8, </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">CPU_N=2,</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> IO_D=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0.5</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">--- CPU 密集型測試 </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">計算運算</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ---</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41883</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41884</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41886</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41888</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41885</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41887</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41889</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41890</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Process</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.1576s</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Thread</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.4108s</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Task finished.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Asyncio</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.4211s</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">--- I/O 密集型測試 </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">網路等待</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ---</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">41879</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Thread</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">I/O</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.5069s</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Asyncio</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Task finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Asyncio</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">I/O</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.5020s</span></span></code></pre>\n<h3 id=\"golang\"><a class=\"anchor\" href=\"#golang\">#</a> Golang</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-go\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">package</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> main</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"</span><span style=\"color:#59873A;--shiki-dark:#80A665\">fmt</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"</span><span style=\"color:#59873A;--shiki-dark:#80A665\">time</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"</span><span style=\"color:#59873A;--shiki-dark:#80A665\">runtime</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"</span><span style=\"color:#59873A;--shiki-dark:#80A665\">sync</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// --- 參數設定 (應與 Python 保持一致) ---</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">const</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> NUM_WORKERS</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 8</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">const</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> N_CPU</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 2</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">const</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> DELAY_IO</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.5</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// --- 任務定義 ---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// 1. CPU 密集型任務 (Goroutine)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">func</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> cpuBoundTask</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">n</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> id</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> wg</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">sync</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">WaitGroup</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    defer</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> wg</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Done</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    i</span><span style=\"color:#999999;--shiki-dark:#666666\"> :=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> n</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 500000</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">        i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">++</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">        _</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 51000.0</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 51000.0</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> /</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.5</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Printf</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">] CPU-Bound Goroutine </span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> finished.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> id</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> id</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// 2. I/O 密集型任務 (Goroutine)</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// 參數 delay_ms 必須是 int 類型，代表毫秒</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">func</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> ioBoundTask</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">delay_ms</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> id</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> wg</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">sync</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">WaitGroup</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    defer</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> wg</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Done</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    // 傳入的 delay_ms 是毫秒，使用 time.Millisecond 進行休眠</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Sleep</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Duration</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">delay_ms</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">Millisecond</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    // 輸出時，將毫秒轉換回秒</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Printf</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">] I/O-Bound Goroutine </span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> finished after </span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%.1f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">s.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> id</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> id</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> float64</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">delay_ms</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">/</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1000.0</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// --- 運行函數 ---</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">func</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> runGoroutines</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">task</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> func</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">int</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">sync</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">WaitGroup</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> task_param</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> float64</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    start</span><span style=\"color:#999999;--shiki-dark:#666666\"> :=</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Now</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    var</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> wg</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> sync</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">WaitGroup</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> i</span><span style=\"color:#999999;--shiki-dark:#666666\"> :=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">=</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> NUM_WORKERS</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">++</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">        wg</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Add</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        go</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> task</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">task_param</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> i</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> &#x26;</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">wg</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    wg</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Wait</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    elapsed</span><span style=\"color:#999999;--shiki-dark:#666666\"> :=</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> time</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Since</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">start</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> elapsed</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Seconds</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// --- 主程式執行區 ---</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">func</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> main</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Printf</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">--- Golang Goroutine 測試 (CPUs: </span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">, N=</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">) ---</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> runtime</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">NumCPU</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> NUM_WORKERS</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    // 1. CPU 密集型任務比較</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Println</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[--- CPU 密集型測試 (計算運算) ---]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    // 【Go 1】Goroutine (CPU)：**適用**，應與 Python Process 接近，因能利用多核</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    time_g_cpu</span><span style=\"color:#999999;--shiki-dark:#666666\"> :=</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> runGoroutines</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">cpuBoundTask</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> N_CPU</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Printf</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">Goroutine (CPU) Total Time: </span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%.4f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> seconds.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> time_g_cpu</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    // 2. I/O 密集型任務比較</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Println</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[--- I/O 密集型測試 (網路等待) ---]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    //【Go 2】Goroutine (I/O)：**最適用**，應與 Python Asyncio 接近，因切換極快，Goroutine (I/O)：輕量級併發</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    // 修正點：將 DELAY_IO (0.5) 乘以 1000 轉為毫秒 (500)，並強制轉換為 int</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    time_g_io</span><span style=\"color:#999999;--shiki-dark:#666666\"> :=</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> runGoroutines</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">ioBoundTask</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> int</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">DELAY_IO</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> *</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1000</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">    fmt</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">Printf</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">Goroutine (I/O) Total Time: </span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">%.4f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> seconds.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\n</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> time_g_io</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span></code></pre>\n<p>輸出結果：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">---</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Golang</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Goroutine</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 測試</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">CPUs: </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">10,</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> N=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">8</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">--- CPU 密集型測試 </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">計算運算</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ---</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">8</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 8 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">4</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 4 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">2</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 2 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 1 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">3</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 3 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">6</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 6 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">5</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 5 finished.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">7</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> CPU-Bound Goroutine 7 finished.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Goroutine</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">CPU</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.0008 seconds.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">--- I/O 密集型測試 </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">網路等待</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ---</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 1 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">3</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 3 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">8</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 8 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">5</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 5 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">6</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 6 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">2</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 2 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">4</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 4 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">7</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> I/O-Bound Goroutine 7 finished after 0.5s.</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Goroutine</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">I/O</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Total Time: 0.5024 seconds.</span></span></code></pre>\n<h3 id=\"為何-python-輸出多且分散而-golang-輸出少且集中\"><a class=\"anchor\" href=\"#為何-python-輸出多且分散而-golang-輸出少且集中\">#</a> 為何 Python 輸出多且分散，而 Golang 輸出少且集中？</h3>\n<h4 id=\"1-python-輸出多的原因即時-io-與競爭\"><a class=\"anchor\" href=\"#1-python-輸出多的原因即時-io-與競爭\">#</a> 1. Python 輸出多的原因：即時 I/O 與競爭</h4>\n<p>Python 輸出分散且量多，源於其 I/O 模型的特性：</p>\n<ul>\n<li><strong>行緩衝 (Line Buffering):</strong> Python 的 <code>print()</code> 預設是行緩衝，輸出一旦遇到換行符 (<code>\\n</code>)，會<strong>立即</strong>送往作業系統。</li>\n<li><strong>多單元競爭：</strong> 在 Process、Thread 或 Asyncio 環境中，每個獨立執行單元完成工作後，會<strong>即時且無序地</strong>競爭寫入標準輸出。</li>\n</ul>\n<p><strong>總結：</strong> Python 的輸出是分散的，因為它會即時顯示每個執行單元的完成狀態。</p>\n<h4 id=\"2-golang-輸出少的原因runtime-集中管理\"><a class=\"anchor\" href=\"#2-golang-輸出少的原因runtime-集中管理\">#</a> 2. Golang 輸出少的原因：Runtime 集中管理</h4>\n<p>Golang 輸出集中且量少，體現了 Go Runtime 的設計優化：</p>\n<ul>\n<li><strong>Runtime I/O 緩衝：</strong> Go 語言的 Runtime 會對 Goroutine 的 I/O 進行更積極的<strong>集中緩衝和優化</strong>。它傾向於收集多個 Goroutine 的輸出，然後透過較少的系統呼叫<strong>一次性</strong>寫入終端機。</li>\n<li><strong>極致的並行效率：</strong> 由於 Goroutine 處理 CPU 任務的速度極快（微秒級），即使輸出是分散的，也因時間間隔太短而被終端機視為<strong>瞬間完成的批次輸出</strong>。</li>\n</ul>\n<p><strong>總結：</strong> Golang 的輸出是集中的，是 Go Runtime 為了提高效率而進行的 I/O 批次處理。</p>\n<h2 id=\"結果\"><a class=\"anchor\" href=\"#結果\">#</a> 結果</h2>\n<h3 id=\"1-cpu-密集型效能python-gil-的量化成本\"><a class=\"anchor\" href=\"#1-cpu-密集型效能python-gil-的量化成本\">#</a> 1. CPU 密集型效能：Python GIL 的量化成本</h3>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">機制</th>\n<th style=\"text-align:left\">總時間 (Total Time)</th>\n<th style=\"text-align:left\">效能比 (相對於 Goroutine)</th>\n<th style=\"text-align:left\"><strong>結論</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\"><strong>Golang Goroutine</strong></td>\n<td style=\"text-align:left\"><strong><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mn>0.0007</mn><mtext>s</mtext><mo>∼</mo><mn>0.0008</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">0.0007\\text{s} \\sim 0.0008\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.0007</span><span class=\"mord text\"><span class=\"mord\">s</span></span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">∼</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.0008</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></strong></td>\n<td style=\"text-align:left\"><strong>基線 (1.0x)</strong></td>\n<td style=\"text-align:left\"><strong>極致並行：</strong> Goroutine 展現了 Go Runtime M:N 排程器的超低延遲和高效能核心利用。</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Python Process</strong></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mn>0.1576</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">0.1576\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.1576</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mrow><mn mathvariant=\"bold\">200</mn><mi mathvariant=\"bold\">x</mi></mrow></mrow><annotation encoding=\"application/x-tex\">\\approx \\mathbf{200x}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\"><span class=\"mord mathbf\">200x</span></span></span></span></span> 慢</td>\n<td style=\"text-align:left\"><strong>高開銷並行：</strong> 雖然實現並行，但 Process 啟動和資源隔離的成本導致總耗時遠高於 Goroutine。</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Python Thread/Asyncio</strong></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mn>0.41</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">\\approx 0.41\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.41</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mrow><mn mathvariant=\"bold\">500</mn><mi mathvariant=\"bold\">x</mi></mrow></mrow><annotation encoding=\"application/x-tex\">\\approx \\mathbf{500x}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\"><span class=\"mord mathbf\">500x</span></span></span></span></span> 慢</td>\n<td style=\"text-align:left\"><strong>GIL 懲罰：</strong> 總時間鎖定在單核運算時間，證明 <strong>GIL 成功地將 8 核心系統的效能退化為串行。</strong></td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"2-io-密集型效能輕量級併發的等價性\"><a class=\"anchor\" href=\"#2-io-密集型效能輕量級併發的等價性\">#</a> 2. I/O 密集型效能：輕量級併發的等價性</h3>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">機制</th>\n<th style=\"text-align:left\">總時間 (Total Time)</th>\n<th style=\"text-align:left\"><strong>總結分析</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\"><strong>Golang Goroutine</strong></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mn>0.501</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">\\approx 0.501\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.501</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></td>\n<td style=\"text-align:left\"><strong>基準：</strong> 高效且穩定，切換開銷可忽略。</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Python Asyncio</strong></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mn>0.502</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">\\approx 0.502\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.502</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></td>\n<td style=\"text-align:left\"><strong>極致對標：</strong> 證明 Python 協程在 I/O 任務上的效率與 Goroutine 處於同一量級。</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Python Thread</strong></td>\n<td style=\"text-align:left\"><span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mo>≈</mo><mn>0.506</mn><mtext>s</mtext></mrow><annotation encoding=\"application/x-tex\">\\approx 0.506\\text{s}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4831em;\"></span><span class=\"mrel\">≈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6444em;\"></span><span class=\"mord\">0.506</span><span class=\"mord text\"><span class=\"mord\">s</span></span></span></span></span></td>\n<td style=\"text-align:left\"><strong>微小開銷：</strong> 證實傳統 OS 執行緒的 Context Switching 成本高於用戶級協程。</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"3-輸出-io-模型差異緩衝與調度策略\"><a class=\"anchor\" href=\"#3-輸出-io-模型差異緩衝與調度策略\">#</a> 3. 輸出 I/O 模型差異：緩衝與調度策略</h3>\n<p>Go 和 Python 在終端機輸出上的顯著差異，體現了各自語言對 I/O 策略的選擇：</p>\n<ul>\n<li><strong>Golang (集中輸出)：</strong> 體現了 Go Runtime 對 I/O 的<strong>積極緩衝和批次處理</strong>。這種策略優化了系統資源，是高性能伺服器語言的典型特徵。</li>\n<li><strong>Python (分散輸出)：</strong> 體現了 Python 對 <strong>I/O 即時性</strong>和 <strong>調試可觀察性</strong>的偏好（如行緩衝）。這種設計在多進程/多執行緒環境下會導致輸出競爭，是效率較低的策略。</li>\n</ul>\n<h3 id=\"結論程式設計的哲學選擇\"><a class=\"anchor\" href=\"#結論程式設計的哲學選擇\">#</a> 結論：程式設計的哲學選擇</h3>\n<p>本次量化測試提供了選擇語言架構的最終依據：</p>\n<ol>\n<li><strong>CPU 密集型</strong>：Golang 的 <strong>Goroutine</strong> 提供了壓倒性的效能。Python 僅限於使用高開銷的 <strong>multiprocessing</strong>。</li>\n<li><strong>I/O 密集型</strong>：Python 的 <strong>Asyncio</strong> 和 Golang 的 <strong>Goroutine</strong> 均為優秀的解決方案，效率上不分伯仲。選擇將取決於生態系統或對全棧語言的需求。</li>\n</ol>\n",
            "tags": [
                "Python",
                "Golang",
                "Concurrency"
            ]
        },
        {
            "id": "https://akebee.com/rust-compile/",
            "url": "https://akebee.com/rust-compile/",
            "title": "PyO3 + maturin，從 Rust 編譯成 Python Extension 連同 .pyi 型別存根打包進 wheel 全流程",
            "date_published": "2025-10-17T02:13:33.000Z",
            "content_html": "<h2 id=\"建立-rust-專案\"><a class=\"anchor\" href=\"#建立-rust-專案\">#</a> 建立 Rust 專案</h2>\n<p>建立 Rust 資料夾</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">mkdir</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_rust_lib</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">cd</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_rust_lib</span></span></code></pre>\n<p>建立 Cargo.toml（Rust 的設定檔</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-toml\"><span class=\"line\"><span style=\"color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic\">Cargo.toml 內容：</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#59873A;--shiki-dark:#80A665\">package</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">name</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">my_rust_lib</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">version</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">0.1.0</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">edition</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">2021</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#59873A;--shiki-dark:#80A665\">lib</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">name</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">my_rust_lib</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">crate-type</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">cdylib</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # ← 關鍵！編譯成動態庫供 </span></span>\n<span class=\"line\"><span style=\"color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic\">Python 使用</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#59873A;--shiki-dark:#80A665\">dependencies</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">pyo3</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> version</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">0.26</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> features</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">extension-module</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # ← PyO3 橋接</span></span></code></pre>\n<p>建立 pyproject.toml（Python 的設定檔）</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-toml\"><span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#59873A;--shiki-dark:#80A665\">build-system</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">requires</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">maturin>=1.8,&#x3C;2.0</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">build-backend</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">maturin</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#59873A;--shiki-dark:#80A665\">project</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">name</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">my_rust_lib</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">version</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">0.1.0</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">description</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">A Python extension module written in Rust using PyO3</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">requires-python</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">>=3.11,&#x3C;3.15</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#59873A;--shiki-dark:#80A665\">tool</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">maturin</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">features</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">pyo3/extension-module</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">include</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">my_rust_lib/**/*</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<p><code>features = [&quot;pyo3/extension-module&quot;]</code> 就是告訴編譯器靶crate 打包成可被 python import 的模組</p>\n<p>如果沒加，編譯後的 .so 會是「一般動態函式庫」而不是「Python extension module」。在 Python 直接 import your_module 時可能會失敗，因為缺少 PyO3 預期的初始化符號。</p>\n<blockquote>\n<p>如果只是做一個純 Rust 套件，然後透過 FFI (cffi, ctypes) 去 call .so，那就不需要 pyo3/extension-module。</p>\n</blockquote>\n<h3 id=\"撰寫-rust-程式碼\"><a class=\"anchor\" href=\"#撰寫-rust-程式碼\">#</a> 撰寫 Rust 程式碼</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-rust\"><span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">src</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">/</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">lib</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">rs 結構：</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">use</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> pyo3</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">::</span><span style=\"color:#59873A;--shiki-dark:#80A665\">prelude</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">::*</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  // ← 導入 PyO3</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">....</span></span></code></pre>\n<h2 id=\"編譯指令\"><a class=\"anchor\" href=\"#編譯指令\">#</a> 編譯指令</h2>\n<p>編譯並輸出 wheel 套件檔案，會在 target/wheels/ 產生 .whl 安裝包。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">maturin</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> build</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --release</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -m</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_rust_lib/Cargo.toml</span></span></code></pre>\n<p><img loading=\"lazy\" src=\"/images/2025/rust-compile/target.png\" alt=\"target\" /></p>\n<h2 id=\"安裝模組\"><a class=\"anchor\" href=\"#安裝模組\">#</a> 安裝模組</h2>\n<p>把你剛編譯出來的 Rust-Python 模組 wheel，強制安裝到當前 Python 環境。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">uv</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> pip</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> install</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --force-reinstall</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_rust_lib/target/wheels/my_rust_lib-</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">*</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">.whl</span></span></code></pre>\n<p>直接點 import 的類，或是直接去 <code>.venv/lib/python3.13/site-packages/</code> 應就能看到編譯好的模組檔</p>\n<p><img loading=\"lazy\" src=\"/images/2025/rust-compile/lib.png\" alt=\"lib\" /></p>\n<h2 id=\"python-調用-rust-模組\"><a class=\"anchor\" href=\"#python-調用-rust-模組\">#</a> Python 調用 Rust 模組</h2>\n<p>就可以在 Python 中直接調用 Rust 編譯好的模組</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> my_rust_lib </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># ← 直接導入，就像普通 Python 模組</span></span></code></pre>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">┌─────────────────────────────────────────────────┐</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">           Python</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 程式</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                            │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  import</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_rust_lib</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                          │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  my_rust_lib.correct_text</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">...</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">               │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">└──────────────────┬──────────────────────────────┘</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">                   │</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">Python </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">C</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> API</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">                   ↓</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">┌─────────────────────────────────────────────────┐</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">              PyO3</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 橋接層</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                         │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 型別轉換</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                                      │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 錯誤處理</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                                      │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> GIL</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 管理</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                                      │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">└──────────────────┬──────────────────────────────┘</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">                   ↓</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">┌─────────────────────────────────────────────────┐</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">         Rust</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 編譯的機器碼</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                        │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  my_rust_lib.so</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                              │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> correct_text</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:rgba(255, 18, 18, 0.8);--shiki-dark:rgba(255, 18, 18, 0.8)\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">native </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">code]</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                 │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> pinyin</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 轉換</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:rgba(255, 18, 18, 0.8);--shiki-dark:rgba(255, 18, 18, 0.8)\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">native </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">code]</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                    │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> edit_distance</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:rgba(255, 18, 18, 0.8);--shiki-dark:rgba(255, 18, 18, 0.8)\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">native </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">code]</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                  │</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">└─────────────────────────────────────────────────┘</span></span></code></pre>\n<p>所以走一次流程大概就是</p>\n<ol>\n<li>編譯 Rust 程式碼 → 生成優化的機器碼，.so 動態庫</li>\n<li>鏈接 Python C API → 透過 PyO3 橋接</li>\n<li>生成 .so 檔案 → my_rust_lib.cpython-313-darwin.so</li>\n<li>安裝到虛擬環境 → <code>.venv/lib/python3.13/site-packages/my_rust_lib/</code></li>\n</ol>\n",
            "tags": [
                "Python",
                "Rust",
                "PyO3",
                "maturin"
            ]
        },
        {
            "id": "https://akebee.com/python-to-rust/",
            "url": "https://akebee.com/python-to-rust/",
            "title": "實測 Rust 與 Python 在多執行緒併發下的 Throughput 與 Latency 表現比較",
            "date_published": "2025-10-13T08:01:45.000Z",
            "content_html": "<h2 id=\"locust-蝗蟲壓力測試工具\"><a class=\"anchor\" href=\"#locust-蝗蟲壓力測試工具\">#</a> locust 蝗蟲壓力測試工具</h2>\n<h2 id=\"為何-rust-在-cpu-密集運算下會比-python-快\"><a class=\"anchor\" href=\"#為何-rust-在-cpu-密集運算下會比-python-快\">#</a> 為何 Rust 在 CPU 密集運算下會比 Python 快？</h2>\n<p>Rust 快的原因：</p>\n<ol>\n<li>✅ 編譯成機器碼 - 更底層，無中間層</li>\n<li>✅ 編譯器優化 - LLVM 做大量優化</li>\n<li>✅ 無 GIL - 真正的多核並行</li>\n<li>✅ 零成本抽象 - 高階語法，無性能損失</li>\n<li>✅ 記憶體管理 - 編譯時確定，無 GC 開銷</li>\n</ol>\n<p>Python 慢的原因：</p>\n<ol>\n<li>❌ 解釋執行 - 每次都要翻譯</li>\n<li>❌ 動態型別 - 運行時檢查型別</li>\n<li>❌ GIL 限制 - 單核執行</li>\n<li>❌ 記憶體開銷 - 動態分配 + 垃圾回收</li>\n</ol>\n<h3 id=\"靜態型別-vs-動態型別\"><a class=\"anchor\" href=\"#靜態型別-vs-動態型別\">#</a> 靜態型別 vs 動態型別</h3>\n<ol>\n<li>Rust：編譯期就知道所有變數型別，能幫助編譯器做最佳化。</li>\n<li>Python：每次運算（例如 a + b）都要檢查 a 和 b 的型別，甚至呼叫底層 C 函數，增加 overhead。</li>\n</ol>\n<p>比如：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-py\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># Python 要判斷 int/float/str，甚至要拋錯</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">result </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> b  </span></span></code></pre>\n<p>Rust 編譯期就知道 a 和 b 是什麼型別，直接生成對應的機器碼</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-rs\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// Rust 在編譯期就確定型別，CPU 直接執行加法</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">let</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> result</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> a</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> +</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> b</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span></code></pre>\n<h3 id=\"python-的-gil-問題\"><a class=\"anchor\" href=\"#python-的-gil-問題\">#</a> Python 的 GIL 問題</h3>\n<p>即使有 4 個 CPU 核心，同一時間只能有 1 個線程執行 Python 程式碼</p>\n<p>Python：</p>\n<blockquote>\n<p>Thread 1: 執行中 🟢<br />\nThread 2: 等待 GIL 🔴<br />\nThread 3: 等待 GIL 🔴<br />\nThread 4: 等待 GIL 🔴</p>\n</blockquote>\n<p>Rust 無 GIL： 可以真正利用多核 CPU</p>\n<blockquote>\n<p>Thread 1: 執行中 🟢<br />\nThread 2: 執行中 🟢<br />\nThread 3: 執行中 🟢<br />\nThread 4: 執行中 🟢</p>\n</blockquote>\n<p>但 Python 的優勢是開發速度快！ 所以我們用 Rust 重寫需要盡可能增加速度的部分，其他部分就保留 Python 👍</p>\n<blockquote>\n<p>「Rust for speed, Python for productivity」</p>\n</blockquote>\n<p>結論:Rust 靠「靜態型別 + 編譯最佳化 + 真正並行 + 記憶體安全」贏在效能；Python 靠「動態型別 + 解譯執行 + GIL」輸在速度，但贏在生態系和開發速度。兩者搭配常常是最好的解法。</p>\n",
            "tags": [
                "Python",
                "Rust",
                "PyO3"
            ]
        },
        {
            "id": "https://akebee.com/docker-init-db-tables/",
            "url": "https://akebee.com/docker-init-db-tables/",
            "title": "docker compose 啟動服務時，自動初始化(init)資料庫跟資料(seed) - PostgreSQL",
            "date_published": "2025-10-03T09:00:15.000Z",
            "content_html": "<h2 id=\"目的\"><a class=\"anchor\" href=\"#目的\">#</a> 目的？</h2>\n<p>以往在用 docker compose 快速啟動服務，常常會遇到：</p>\n<ul>\n<li>在一台全新的機器或開發環境中，都需要手動進入docker內部db容器，手動執行初始化(init)資料庫結構的 SQL 腳本，接著再執行資料(seed)的 SQL 腳本來填充初始資料</li>\n<li>流程不僅繁瑣，而且容易出錯，特別是在多個環境（如開發、測試、生產）中部署時</li>\n</ul>\n<p>比如在某專案中，固定會有個 <code>products</code> 的資料表，並且每次部署到新主機第一次啟動時，去打端點都一定會跳<code>sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedTable) relation &quot;table_name&quot; does not exist</code> 資料表不存在的錯誤，都得透過手動執行 Python 腳本 create_db_data.py + SQLAlchemy 語法來建表與插入資料</p>\n<h2 id=\"使用-postgresql-官方-image-docker-entrypoint-initdbd-機制\"><a class=\"anchor\" href=\"#使用-postgresql-官方-image-docker-entrypoint-initdbd-機制\">#</a> 使用 PostgreSQL 官方 image docker-entrypoint-initdb.d/ 機制</h2>\n<p>PostgreSQL 官方 image 內建的 <code>docker-entrypoint.sh</code> 會在容器初始化（第一次建立資料 volume）時，自動執行 <code>/docker-entrypoint-initdb.d/</code> 目錄底下的 <code>.sql</code> 或 <code>.sh</code> 檔案。</p>\n<p>只要是在 <code>/docker-entrypoint-initdb.d/</code> 的 <code>.sql</code> 或 <code>.sh</code> 檔案，Postgres 會在 <strong>第一次啟動資料庫</strong> 時自動執行</p>\n<h3 id=\"初始化-sql-檔案\"><a class=\"anchor\" href=\"#初始化-sql-檔案\">#</a> 初始化 SQL 檔案</h3>\n<p>創建 <code>init-scripts</code> 資料夾並在底下建立了三個 SQL 檔</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">project/</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">├─</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> docker-compose.yaml</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">├─</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> init-scripts/</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   ├─</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 001_enable_extensions.sql</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   ├─</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 002_create_table.sql</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">│</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   └─</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 003_seed_data.sql</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">...</span></span></code></pre>\n<p>在<code>001_enable_extensions.sql</code>中，確保 pgcrypto 可用（因為用了 gen_random_uuid()）產生 UUID</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sql\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">CREATE</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> EXTENSION </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">IF</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> NOT</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> EXISTS</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">pgcrypto</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">;</span></span></code></pre>\n<p>在<code>002_create_table.sql</code>，寫建立資料表的 SQL</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sql\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">CREATE</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> TABLE</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> IF</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> NOT</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> EXISTS</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> products </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    id UUID </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">PRIMARY KEY</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> DEFAULT</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> gen_random_uuid</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    name</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> VARCHAR</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">100</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">UNIQUE</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> NOT NULL</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    description</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> TEXT</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    price </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">NUMERIC</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">10</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">2</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">NOT NULL</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    in_stock </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">BOOLEAN</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> NOT NULL</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> DEFAULT</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> TRUE,</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    created_at </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">TIMESTAMP</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> NOT NULL</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> DEFAULT</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> now</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    updated_at </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">TIMESTAMP</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> NOT NULL</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> DEFAULT</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> now</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">;</span></span></code></pre>\n<p>插入固定資料</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sql\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">INSERT INTO</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> products </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">name</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">description</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, price, in_stock</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">VALUES</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">iPhone 15</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">最新款智慧型手機</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">32900</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">.</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">00</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, TRUE</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">MacBook Air</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">輕薄筆電</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">42900</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">.</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">00</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, TRUE</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">,</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">AirPods Pro</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">主動降噪無線耳機</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, </span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">7990</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">.</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">00</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">, FALSE</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">;</span></span></code></pre>\n<h2 id=\"在-docker-composeyaml-中掛載-init-scripts\"><a class=\"anchor\" href=\"#在-docker-composeyaml-中掛載-init-scripts\">#</a> 在 docker-compose.yaml 中掛載 init-scripts</h2>\n<p>最後在 <code>docker-compose.yaml</code> 中掛載 <code>init-scripts</code> 目錄</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">services</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  db</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    image</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> postgres:16</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    container_name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_postgres</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    restart</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> always</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    environment</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      POSTGRES_USER</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> myuser</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      POSTGRES_PASSWORD</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> mypassword</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      POSTGRES_DB</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> mydb</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    volumes</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">      -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ./init-scripts:/docker-entrypoint-initdb.d</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">      -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> postgres_data:/var/lib/postgresql/data</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    ports</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">      -</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">5432:5432</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">volumes</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  postgres_data</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span></code></pre>\n<p>這樣每次啟動時容器時，Postgres 容器就會：</p>\n<ol>\n<li>執行 001_enable_extensions.sql → 啟用 UUID</li>\n<li>執行 002_create_table.sql → 建立 products 資料表</li>\n<li>執行 003_seed_data.sql → 插入三筆固定商品</li>\n</ol>\n<h2 id=\"結果\"><a class=\"anchor\" href=\"#結果\">#</a> 結果</h2>\n<p>啟動後，我們可以進容器查詢資料：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> exec</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -it</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_postgres</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> psql</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -U</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> myuser</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> mydb</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -c</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">SELECT * FROM products;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span></code></pre>\n<p>應就能看到類似結果：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">id        |    name     |     description     |  price  | in_stock |        created_at        |        updated_at</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        </span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">---</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">-----------------------------------+-------------+---------------------+---------+----------+--------------------------</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 5a32cda3-28a4-4f4f-8df6-41d4a8e2f01d | iPhone 15   | 最新款智慧型手機    | 32900.0 | t        | 2025-10-07 13:00:00+00   | 2025-10-07 13:00:00+00</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 182d9cb5-4e43-44c6-9e35-9a933b292c21 | MacBook Air | 輕薄筆電           | 42900.0 | t        | 2025-10-07 13:00:00+00   | 2025-10-07 13:00:00+00</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 3efcd124-46b9-4c02-850b-0f991a216af8 | AirPods Pro | 主動降噪無線耳機    |  7990.0 | f        | 2025-10-07 13:00:00+00   | 2025-10-07 13:00:00+00</span></span></code></pre>\n",
            "tags": [
                "Linux",
                "Docker",
                "DevOps",
                "PostgreSQL"
            ]
        },
        {
            "id": "https://akebee.com/docker-tutorial/",
            "url": "https://akebee.com/docker-tutorial/",
            "title": "Docker - 常用指令筆記 Image、Volume、Container 管理",
            "date_published": "2025-10-03T09:00:15.000Z",
            "content_html": "<h2 id=\"docker-ps\"><a class=\"anchor\" href=\"#docker-ps\">#</a> docker ps</h2>\n<p>只顯示正在執行的容器</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ps</span></span></code></pre>\n<p>顯示所有容器（包含執行中、已停止、已退出）</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ps</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -a</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">  </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># or</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ps</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --all</span></span></code></pre>\n<p>顯示最近建立的容器</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ps</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -l</span></span></code></pre>\n<p>顯示最近建立的 N 個容器（例：<code>docker ps -n 3</code>）</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ps</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">數</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">字</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span></code></pre>\n<h2 id=\"管理-docker-data-相關指令\"><a class=\"anchor\" href=\"#管理-docker-data-相關指令\">#</a> 管理 Docker data 相關指令</h2>\n<p>查看目前有哪些 volume，很常會有需要清理一些沒在使用的、或只是暫時創建來測試用的一些 data，這些 data = docker 的 volume</p>\n<h3 id=\"列出目前有哪些-volume\"><a class=\"anchor\" href=\"#列出目前有哪些-volume\">#</a> 列出目前有哪些 volume</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ls</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">DRIVER</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">    VOLUME</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> NAME</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     a88a3848877ac88f427aa5cefe20fc3b154f9fc6c8f8a91233ec33ec83ea497f</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     f91f8d6e293f00bd830e5773f6ecbb14c0e2a8898dfbe39a7db06df2dff20f58</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     xxx_my_logs</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     xxx_postgres_data</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     xxx_redis_data</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     yyy_backend_media</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     yyy_backend_static</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">local</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">     yyy_postgres_data</span></span></code></pre>\n<h3 id=\"刪除指定-volume\"><a class=\"anchor\" href=\"#刪除指定-volume\">#</a> 刪除指定 volume</h3>\n<p>比如說 <code>xxx_postgres_data</code> 這個 postgres 的 data 是舊的，有新的 db schema 要重新建一個新的 volume，這時候就可以執行</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> rm</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">VOLUME</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> NAM</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">E</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span></code></pre>\n<p>一次刪除多個 volumes</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> rm</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume1</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume2</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume3</span></span></code></pre>\n<h3 id=\"搭配-container-移除\"><a class=\"anchor\" href=\"#搭配-container-移除\">#</a> 搭配 Container 移除</h3>\n<p>如果 container 移除時想一併刪除匿名的 volumes：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> rm</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -v</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">CONTAINER</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ID</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> or</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> NAM</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">E</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span></code></pre>\n<h3 id=\"清除沒被任何-container-使用的-volume\"><a class=\"anchor\" href=\"#清除沒被任何-container-使用的-volume\">#</a> 清除沒被任何 container 使用的 volume</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> volume</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> prune</span></span></code></pre>\n<h2 id=\"根據-docker-image-build-開發-vs-正式環境\"><a class=\"anchor\" href=\"#根據-docker-image-build-開發-vs-正式環境\">#</a> 根據 docker image build 開發 vs 正式環境</h2>\n<p>預設會檢查 image 是否存在，如果不存在就會自動 build（等於 up --build）。如果 image 已存在，不會重新 build，直接用現成的 image 起 container</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> compose</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> up</span></span></code></pre>\n<p>明確要求重新 build image，根據 Dockerfile 與 context，不會啟動容器，只是建好新的 image。常搭配 <code>docker compose up -d</code> 一起用：</p>\n<p>比如剛剛修改了 Dockerfile / 套件依賴，要確保 image 更新</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> compose</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> build</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # 只做 build，不會啟動容器</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> compose</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> build</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --no-cache</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # 確保不使用快取</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> compose</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> up</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -d</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --build</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # build 完後啟動容器並在背景執行</span></span></code></pre>\n<h3 id=\"docker-compose-up-build-vs-docker-compose-build-差異\"><a class=\"anchor\" href=\"#docker-compose-up-build-vs-docker-compose-build-差異\">#</a> docker compose up --build VS docker compose build 差異</h3>\n<table>\n<thead>\n<tr>\n<th>指令</th>\n<th>是否會建新 image</th>\n<th>是否啟動 container</th>\n<th>適合環境</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>docker compose up</code></td>\n<td>如果 image 不存在才建</td>\n<td>✅</td>\n<td>開發快速啟動</td>\n</tr>\n<tr>\n<td><code>docker compose up -d</code></td>\n<td>如果 image 不存在才建</td>\n<td>✅</td>\n<td>開發快速啟動（背景執行）</td>\n</tr>\n<tr>\n<td><code>docker compose up --build</code></td>\n<td>強制重建</td>\n<td>✅</td>\n<td>開發測試，確保 image 更新</td>\n</tr>\n<tr>\n<td><code>docker compose build</code></td>\n<td>✅ 強制重建</td>\n<td>❌</td>\n<td>Production 準備、CI/CD pipeline</td>\n</tr>\n</tbody>\n</table>\n<h3 id=\"清除-images\"><a class=\"anchor\" href=\"#清除-images\">#</a> 清除 images</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 清除「未被任何容器使用」的鏡像 (dangling images)</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> prune</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># -a = all，不只 dangling，會清掉所有目前沒被用到的 image</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> prune</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -a</span></span></code></pre>\n<h2 id=\"logs-查看該-container-輸出的-log\"><a class=\"anchor\" href=\"#logs-查看該-container-輸出的-log\">#</a> logs 查看該 container 輸出的 log</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 查看指定容器的全部日誌</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">container_name_or_i</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">d</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 查看容器即時日誌（持續輸出）</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -f</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">container_name_or_i</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">d</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 查看最新的 100 行日誌</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --tail</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 100</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">container_name_or_i</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">d</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 查看日誌並附帶時間戳</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -t</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">container_name_or_i</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">d</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 實時查看，並只輸出最新 50 行</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -f</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --tail</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 50</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">container_name_or_i</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">d</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span></code></pre>\n<h2 id=\"exec-進入-docker-container-內\"><a class=\"anchor\" href=\"#exec-進入-docker-container-內\">#</a> exec 進入 docker container 內</h2>\n<p>這指令還蠻常用的，要完全搞清楚 docker 的運作原理跟模式，就很常會親自進入 container 裡面查看實際情況，看 container 內的 logs、跑了什麼進程、檔案系統長怎樣、環境變數、網路設定如何..等等。</p>\n<p>參數說明：</p>\n<ul>\n<li><code>-i</code>：互動式（interactive），保持標準輸入(stdin)開啟</li>\n<li><code>-t</code>：分配一個虛擬終端（tty），讓你可以像在終端機一樣操作</li>\n</ul>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-sh\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 進入容器的互動式終端</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> exec</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -it</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">container_name_or_i</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">d</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> bash</span></span></code></pre>\n",
            "tags": [
                "Linux",
                "Docker",
                "DevOps"
            ]
        },
        {
            "id": "https://akebee.com/fastapi-to-gRPC/",
            "url": "https://akebee.com/fastapi-to-gRPC/",
            "title": "從 FastAPI 轉成 gRPC 服務流程筆記",
            "date_published": "2025-09-16T02:37:12.000Z",
            "content_html": "<h2 id=\"重要\"><a class=\"anchor\" href=\"#重要\">#</a> 重要</h2>\n<ol>\n<li>按理來說接口應要跟 pydantic 模型一模一樣</li>\n</ol>\n<h2 id=\"fastapi-vs-grpc-差異\"><a class=\"anchor\" href=\"#fastapi-vs-grpc-差異\">#</a> FastAPI vs gRPC 差異</h2>\n<table>\n<thead>\n<tr>\n<th>特性</th>\n<th>FastAPI (REST)</th>\n<th>gRPC</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>通訊協定</td>\n<td>HTTP/1.1</td>\n<td>HTTP/2</td>\n</tr>\n<tr>\n<td>資料格式</td>\n<td>JSON</td>\n<td>Protocol Buffers (二進位)</td>\n</tr>\n<tr>\n<td>API 定義</td>\n<td>OpenAPI/Swagger</td>\n<td>Proto 檔案</td>\n</tr>\n<tr>\n<td>效能</td>\n<td>較慢</td>\n<td>較快</td>\n</tr>\n<tr>\n<td>瀏覽器支援</td>\n<td>原生支援</td>\n<td>需要額外工具</td>\n</tr>\n<tr>\n<td>串流</td>\n<td>有限支援</td>\n<td>原生支援雙向串流</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"postman-測試-gprc\"><a class=\"anchor\" href=\"#postman-測試-gprc\">#</a> Postman 測試 gPRC</h2>\n",
            "tags": [
                "Python",
                "FastAPI",
                "gRPC",
                "Protobuf"
            ]
        },
        {
            "id": "https://akebee.com/alembic-sql-migration/",
            "url": "https://akebee.com/alembic-sql-migration/",
            "title": "用 Alembic 管理 SQLAlchemy Migration 筆記",
            "date_published": "2025-09-12T02:24:24.000Z",
            "content_html": "<blockquote>\n<p>目標：當遷移歷史混亂或想用現有模型重新建立「乾淨」的初始遷移時，安全地重置 Alembic 狀態並升到最新。</p>\n</blockquote>\n<h2 id=\"tldr-指令清單\"><a class=\"anchor\" href=\"#tldr-指令清單\">#</a> TL;DR — 指令清單</h2>\n<h3 id=\"1-生成初始-migration\"><a class=\"anchor\" href=\"#1-生成初始-migration\">#</a> 1 生成初始 migration</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> revision</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --autogenerate</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -m</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">initial migration</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span></code></pre>\n<h3 id=\"2-執行-migration創建表格\"><a class=\"anchor\" href=\"#2-執行-migration創建表格\">#</a> 2 執行 migration（創建表格）</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> upgrade</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> head</span></span></code></pre>\n<h3 id=\"3-刪除所有遷移檔案如已刪除可跳過\"><a class=\"anchor\" href=\"#3-刪除所有遷移檔案如已刪除可跳過\">#</a> 3 刪除所有遷移檔案（<strong>如已刪除可跳過</strong>）</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">rm</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> alembic/versions/</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">*</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">.py</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">rm</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -rf</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> alembic/versions/__pycache__</span></span></code></pre>\n<h3 id=\"4-清理資料庫版本記錄docker-版\"><a class=\"anchor\" href=\"#4-清理資料庫版本記錄docker-版\">#</a> 4 清理資料庫版本記錄（Docker 版）</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">docker</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> exec</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_app-postgres-1</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  psql</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -U</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> username</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -d</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> my_app</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -c</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">DELETE FROM alembic_version;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span></code></pre>\n<blockquote>\n<p>非 Docker 版本可用：<br />\n<code>psql -h 127.0.0.1 -U username -d my_app -c &quot;DELETE FROM alembic_version;&quot;</code></p>\n</blockquote>\n<h3 id=\"5-檢查-alembic-狀態指定設定檔\"><a class=\"anchor\" href=\"#5-檢查-alembic-狀態指定設定檔\">#</a> 5 檢查 Alembic 狀態（指定設定檔）</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -c</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> alembic.ini</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> current</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 預期：沒有版本記錄（空白）</span></span></code></pre>\n<h3 id=\"6-再次生成初始遷移檔案\"><a class=\"anchor\" href=\"#6-再次生成初始遷移檔案\">#</a> 6 再次生成「初始遷移」檔案</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> revision</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --autogenerate</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -m</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">initial migration</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span></code></pre>\n<h3 id=\"7-執行遷移\"><a class=\"anchor\" href=\"#7-執行遷移\">#</a> 7 執行遷移</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> upgrade</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> head</span></span></code></pre>\n<h3 id=\"8-確認歷史\"><a class=\"anchor\" href=\"#8-確認歷史\">#</a> 8 確認歷史</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> history</span></span></code></pre>\n<hr />\n<h2 id=\"詳細步驟與說明\"><a class=\"anchor\" href=\"#詳細步驟與說明\">#</a> 詳細步驟與說明</h2>\n<h3 id=\"step-12先用現有模型嘗試產生與執行初始遷移\"><a class=\"anchor\" href=\"#step-12先用現有模型嘗試產生與執行初始遷移\">#</a> Step 1–2：先用現有模型嘗試產生與執行初始遷移</h3>\n<p>確定 <code>migrations/env.py</code>（或 <code>alembic/env.py</code>）內的：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 要指向你的 SQLAlchemy Declarative Base</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">target_metadata </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Base</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">metadata</span></span></code></pre>\n<p>已設定正確，並能偵測到模型差異。<code>alembic upgrade head</code> 會將 DB 升到最新。</p>\n<h3 id=\"step-3刪除舊遷移檔\"><a class=\"anchor\" href=\"#step-3刪除舊遷移檔\">#</a> Step 3：刪除舊遷移檔</h3>\n<p>清空 <code>alembic/versions/</code>，避免舊遷移歷史干擾（<strong>務必先備份或在版本控制中操作</strong>）。</p>\n<h3 id=\"step-4清理-db-的-alembic-版本表\"><a class=\"anchor\" href=\"#step-4清理-db-的-alembic-版本表\">#</a> Step 4：清理 DB 的 Alembic 版本表</h3>\n<p><code>alembic_version</code> 通常僅有一列紀錄目前版本。用 <code>DELETE</code> 清空即可。</p>\n<blockquote>\n<p>如果這張表不存在，代表 DB 未受 Alembic 管理，可跳過或稍後用 <code>alembic stamp</code> 對齊。</p>\n</blockquote>\n<h3 id=\"step-5確認狀態\"><a class=\"anchor\" href=\"#step-5確認狀態\">#</a> Step 5：確認狀態</h3>\n<p><code>alembic current</code> 應無輸出（表示 DB 目前未標記任何遷移版本）。</p>\n<h3 id=\"step-67重新產生並執行初始遷移\"><a class=\"anchor\" href=\"#step-67重新產生並執行初始遷移\">#</a> Step 6–7：重新產生並執行「初始遷移」</h3>\n<p><code>--autogenerate</code> 會根據 <code>target_metadata</code> 與資料庫現況比對產生遷移腳本。<strong>請務必審閱遷移檔</strong>（欄位型別、索引、約束），再 <code>upgrade head</code>。</p>\n<h3 id=\"step-8檢視歷史\"><a class=\"anchor\" href=\"#step-8檢視歷史\">#</a> Step 8：檢視歷史</h3>\n<p><code>alembic history</code> 應只顯示你的「初始遷移」與後續新增的遷移（若有）。</p>\n<hr />\n<h2 id=\"注意事項-小陷阱\"><a class=\"anchor\" href=\"#注意事項-小陷阱\">#</a> 注意事項 / 小陷阱</h2>\n<ul>\n<li>\n<p><strong>先備份</strong>：清遷移與改 DB 前，請先 <code>pg_dump</code>（或備份資料卷），避免不可逆損失。</p>\n</li>\n<li>\n<p><strong>autogenerate 不是全能</strong>：對 trigger、部分複雜 constraint 或 <code>server_default</code> 等，可能無法自動偵測，需手動補上。</p>\n</li>\n<li>\n<p><strong>SQLite 例外多</strong>：欄位變更常走「建新表 → 搬資料 → 刪舊表」策略；Alembic 會用 batch mode 協助但仍需審閱。</p>\n</li>\n<li>\n<p><strong>版本標記 vs. 真正遷移</strong>：若 DB schema 已手動對齊，只想「告訴 Alembic 目前是最新」，可改用：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> stamp</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> head</span></span></code></pre>\n<p>這不會執行任何 SQL，只會寫入版本表。</p>\n</li>\n<li>\n<p><strong>多人開發分支</strong>：若 <code>alembic heads</code> 顯示多個 head，需先 <code>alembic merge -m &quot;merge heads&quot; &lt;revA&gt; &lt;revB&gt;</code> 合併，再 <code>upgrade head</code>。</p>\n</li>\n<li>\n<p><strong>設定檔路徑</strong>：多環境時可用 <code>-c path/to/alembic.ini</code> 指定；多 DB 則可用 <code>-n &lt;section&gt;</code> 選擇命名段落。</p>\n</li>\n<li>\n<p><strong>傳遞自訂參數</strong>：<code>alembic -x key=value ...</code> 可在 <code>env.py</code> 用 <code>context.get_x_argument(as_dictionary=True)</code> 讀取（例如 <code>-x schema=public</code>）。</p>\n</li>\n</ul>\n<hr />\n<h2 id=\"驗證清單\"><a class=\"anchor\" href=\"#驗證清單\">#</a> 驗證清單</h2>\n<ul class=\"task-list\">\n<li class=\"task-list-item\"><input type=\"checkbox\" id=\"cbx_0\" disabled=\"true\" /><label for=\"cbx_0\"> <code>alembic current</code> 顯示為最新（或你預期的版本）</label></li>\n<li class=\"task-list-item\"><input type=\"checkbox\" id=\"cbx_1\" disabled=\"true\" /><label for=\"cbx_1\"> <code>alembic heads</code> 僅一個 head（或已完成合併）</label></li>\n<li class=\"task-list-item\"><input type=\"checkbox\" id=\"cbx_2\" disabled=\"true\" /><label for=\"cbx_2\"> 重要表格/索引/約束皆已建立</label></li>\n<li class=\"task-list-item\"><input type=\"checkbox\" id=\"cbx_3\" disabled=\"true\" /><label for=\"cbx_3\"> 上線前在 staging 跑過 <code>alembic upgrade head --sql</code> 檢視 SQL（必要時）</label></li>\n<li class=\"task-list-item\"><input type=\"checkbox\" id=\"cbx_4\" disabled=\"true\" /><label for=\"cbx_4\"> PR 內附遷移檔，且已 code review</label></li>\n</ul>\n<hr />\n<h2 id=\"參考補充指令\"><a class=\"anchor\" href=\"#參考補充指令\">#</a> 參考補充指令</h2>\n<ul>\n<li>\n<p>顯示詳細歷史：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> history</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --verbose</span></span></code></pre>\n</li>\n<li>\n<p>顯示指定版本資訊：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> show</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">re</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">v</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span></span></code></pre>\n</li>\n<li>\n<p>只產出 SQL（不執行）：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> upgrade</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> head</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --sql</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> upgrade.sql</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">alembic</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> downgrade</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -1</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --sql</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> downgrade.sql</span></span></code></pre>\n</li>\n</ul>\n<hr />\n<p><em>以上流程親測適用：Docker + PostgreSQL（容器名 <code>my_app-postgres-1</code>、DB <code>my_app</code>、使用者 <code>username</code>）。依你的環境調整容器名、帳號與資料庫名稱即可。</em></p>\n",
            "tags": [
                "Python",
                "SQLAlchemy",
                "Alembic"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-ReverseLinkedList/",
            "url": "https://akebee.com/leetcode-ReverseLinkedList/",
            "title": "LeetCode 獨自升級紀 - [Linked List] Reverse Linked List (Easy)",
            "date_published": "2025-08-30T07:54:09.000Z",
            "content_html": "<h2 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h2>\n<p>給定一個單向 Linked List，反轉該鏈表並返回反轉後的頭節點。</p>\n<p><a href=\"https://leetcode.com/problems/reverse-linked-list/description/\">題目</a></p>\n<p>Example:</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-plaintext\"><span class=\"line\"><span>Input: head = [1,2,3,4,5]</span></span>\n<span class=\"line\"><span>Output: [5,4,3,2,1]</span></span></code></pre>\n<h2 id=\"觀念\"><a class=\"anchor\" href=\"#觀念\">#</a> 觀念</h2>\n<p>反正就記得，反轉一條鏈表，就是<code>把每個節點的 next 指標指向前一個節點</code></p>\n<table>\n<thead>\n<tr>\n<th>特性</th>\n<th>Python list (<code>[]</code>)</th>\n<th>Linked List</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>底層結構</td>\n<td>動態陣列 (連續記憶體)</td>\n<td>節點 (物件) + 指標</td>\n</tr>\n<tr>\n<td>存取第 i 個元素</td>\n<td>O(1)</td>\n<td>O(n) (要走訪)</td>\n</tr>\n<tr>\n<td>插入 / 刪除</td>\n<td>O(n) (中間要搬移)</td>\n<td>O(1) (只改指標)</td>\n</tr>\n<tr>\n<td>記憶體配置</td>\n<td>連續</td>\n<td>不連續</td>\n</tr>\n<tr>\n<td>適合情境</td>\n<td>頻繁隨機存取</td>\n<td>頻繁插入 / 刪除</td>\n</tr>\n<tr>\n<td>5</td>\n<td><code>5 → 4 → 3 → 2 → 1 → None</code></td>\n<td>5 的 next 指向 4，curr 變 None</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"linked-list\"><a class=\"anchor\" href=\"#linked-list\">#</a> Linked List</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">  def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> reverseList</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">head</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ListNode</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> ListNode</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">      prev</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> curr </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> None</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> head</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">      while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> curr</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">          temp </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> curr</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">next  </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 先暫存下一個節點，避免斷鏈 #</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">          curr</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">next </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> prev  </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 把 curr 的 next 指向 prev，完成反轉，變 None &#x3C;- 1 &#x3C;- ...</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">          prev </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> curr  </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 把目前的節點 curr 指向 prev</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">          curr </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> temp  </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># curr 指向下一個節點</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">      return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> prev </span></span></code></pre>\n<p>逐步拆解執行流程：</p>\n<table>\n<thead>\n<tr>\n<th>Step</th>\n<th>curr</th>\n<th>temp = curr.next</th>\n<th>執行 <code>curr.next = prev</code> 後</th>\n<th>prev = curr</th>\n<th>curr = temp</th>\n<th>prev 指向的鏈表</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0 (初始)</td>\n<td>1</td>\n<td>-</td>\n<td>-</td>\n<td>None</td>\n<td>1</td>\n<td>None</td>\n</tr>\n<tr>\n<td>1</td>\n<td>1</td>\n<td>2</td>\n<td><code>1 → None</code></td>\n<td>1</td>\n<td>2</td>\n<td>1 → None</td>\n</tr>\n<tr>\n<td>2</td>\n<td>2</td>\n<td>3</td>\n<td><code>2 → 1 → None</code></td>\n<td>2</td>\n<td>3</td>\n<td>2 → 1 → None</td>\n</tr>\n<tr>\n<td>3</td>\n<td>3</td>\n<td>4</td>\n<td><code>3 → 2 → 1 → None</code></td>\n<td>3</td>\n<td>4</td>\n<td>3 → 2 → 1 → None</td>\n</tr>\n<tr>\n<td>4</td>\n<td>4</td>\n<td>5</td>\n<td><code>4 → 3 → 2 → 1 → None</code></td>\n<td>4</td>\n<td>5</td>\n<td>4 → 3 → 2 → 1 → None</td>\n</tr>\n<tr>\n<td>5</td>\n<td>5</td>\n<td>None</td>\n<td><code>5 → 4 → 3 → 2 → 1 → None</code></td>\n<td>5</td>\n<td>None</td>\n<td>5 → 4 → 3 → 2 → 1 → None</td>\n</tr>\n</tbody>\n</table>\n<ul>\n<li>Time Complexity: <code>O(n)</code></li>\n<li>Space Complexity: <code>O(1)</code></li>\n</ul>\n<h2 id=\"參考\"><a class=\"anchor\" href=\"#參考\">#</a> 參考</h2>\n<p>初次學 Linked List 可能會有點抽象，這邊分享當初看到一個非常淺顯易懂的 yt 教學</p>\n<ul>\n<li><a href=\"https://www.youtube.com/watch?v=KRxeMng7fBU&amp;t=356s&amp;ab_channel=GregHogg\">YouTube - Reverse Linked List</a></li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Linked List",
                "Easy"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-LongestSubstringWithoutRepeatingCharacters/",
            "url": "https://akebee.com/leetcode-LongestSubstringWithoutRepeatingCharacters/",
            "title": "LeetCode 獨自升級紀 - [Sliding Window] Longest Substring Without Repeating Characters (Medium)",
            "date_published": "2025-08-27T07:54:09.000Z",
            "content_html": "<h2 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h2>\n<p>輸入一個字串 s，要找出最長的不含重複字元的子字串長度。</p>\n<p>例子：</p>\n<ul>\n<li>s = &quot;<code>abcabcbb</code>&quot; → 答案是 3 (&quot;abc&quot;)</li>\n<li>s = &quot;<code>bbbbb</code>&quot; → 答案是 1 (&quot;b&quot;)</li>\n<li>s = &quot;<code>pwwkew</code>&quot; → 答案是 3 (&quot;wke&quot;)</li>\n</ul>\n<h2 id=\"sliding-window\"><a class=\"anchor\" href=\"#sliding-window\">#</a> Sliding Window</h2>\n<p><img loading=\"lazy\" src=\"/images/2025/leetcode-LongestSubstringWithoutRepeatingCharacters/window-1.png\" alt=\"window-1\" /></p>\n<p>上圖是大概的 Sliding Window 概念，用 <code>set()</code> 去重，並且用兩個指標 <code>l</code> 和 <code>r</code> 來表示目前的子字串範圍，然後不斷地擴展 <code>r</code> 直到遇到重複字元，就移動 <code>l</code> 指標來縮小子字串範圍，將重複字元去除</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> lengthOfLongestSubstring</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> str</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        res </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        charSet </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> set</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        l </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">r</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> charSet</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                charSet</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">remove</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                l </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # 縮減左邊界</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            charSet</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">add</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">r</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # 新增右邊界</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">            # res = max(res, r - l + 1)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            res </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> max</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">res</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">charSet</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> res</span></span></code></pre>\n<p>charSet 表示「從 i 到 j 之間，沒有重複字元的子字串」，其實也就是 = <code>r - l + 1</code>，相同意思，只是我用 <code>len(charSet)</code> 我自己認為更直觀點</p>\n<ul>\n<li>Time Complexity: <code>O(n)</code></li>\n<li>Space Complexity: <code>O(m)</code></li>\n</ul>\n<h2 id=\"brute-force\"><a class=\"anchor\" href=\"#brute-force\">#</a> Brute Force</h2>\n<p>如果 <code>s[j]</code> 的字串已出現過在 charSet 裡面，就跳出 <code>for j in range</code> 迴圈，回到 <code>for i in range</code> 迴圈，重新創建 charSet</p>\n<p>並記錄目前 charSet 的最大長度，每輪更新 <code>max_unique_str_length</code></p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> lengthOfLongestSubstring</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> str</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        max_unique_str_length </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            charset </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> set</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # charSet 永遠表示「從 i 到 j 之間，沒有重複字元的子字串」</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> j </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">j</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> charset</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                    break</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                charset</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">add</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">j</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            max_unique_str_length </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> max</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">max_unique_str_length </span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">charset</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> max_unique_str_length</span></span></code></pre>\n<ul>\n<li>Time Complexity: <code>O(n * m)</code></li>\n<li>Space Complexity: <code>O(m)</code></li>\n</ul>\n<hr />\n<ul>\n<li><a href=\"https://medium.com/hannah-lin/%E6%BC%94%E7%AE%97%E6%B3%95%E7%AD%86%E8%A8%98-sliding-window-72bcba20d294\">很不錯的 Sliding Window 觀念參考筆記</a></li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Medium",
                "Sliding Window"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-Searcha2DMatrix/",
            "url": "https://akebee.com/leetcode-Searcha2DMatrix/",
            "title": "LeetCode 獨自升級紀 - [Binary Search] Search a 2D Matrix (Medium)",
            "date_published": "2025-08-20T07:54:09.000Z",
            "content_html": "<h2 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h2>\n<p><a href=\"https://leetcode.com/problems/search-a-2d-matrix/description/\">題目</a></p>\n<p>Example:</p>\n<ul>\n<li>Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3</li>\n<li>Output: true</li>\n</ul>\n<h2 id=\"binary-search\"><a class=\"anchor\" href=\"#binary-search\">#</a> Binary Search</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> searchMatrix</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> matrix</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> list</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">list</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> int</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> bool</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # double Binary Search</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # 先從 rows 開始找</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        rows </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">matrix</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        top</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> down </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> rows </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # rows 的頭尾</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> top </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> down</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            middle_row </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">top </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> down</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> //</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 2</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            if</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                target </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> matrix</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">middle_row</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">            </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # ，# 如果 target比mid的最右邊最大數字還要大，代表要往下，繼續去更大的 rows 中找</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                top </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> middle_row </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            elif</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> matrix</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">middle_row</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 比這個 rows 的最小數字還要小</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                down </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> middle_row </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                break</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 找到了 數字就在現在的 matrix[middle_row]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        if</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> not</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">top </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> down</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">            # 代表 target 在矩陣的範圍外，直接回傳 False</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">            # 為何還要判斷 if not (top &#x3C;= bot)，不直接 return False？如果沒有這個判斷，程式會繼續往下跑</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">            # 可能誤用 row 變數做第二次在 cols 中找，因為這時候根本沒有任何合法的 row 可以搜尋。</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> False</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        l</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">matrix</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> -</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # matrix[0] = cols 的長度 [[1,3,5,7],[...]]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> l </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            mid </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> //</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 2</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> matrix</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">middle_row</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">mid</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 要更大，左邊的不要</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                l </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> mid </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            elif</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> matrix</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">middle_row</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">mid</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 要更小，右邊的不要</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                r </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> mid </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> True</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> False</span></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(log(m * n))</li>\n<li>SC：O(1)</li>\n</ul>\n<h2 id=\"brute-force\"><a class=\"anchor\" href=\"#brute-force\">#</a> Brute Force</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">matrix</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> c </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> matrix</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> c </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> True</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> False</span></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(m * n)</li>\n<li>SC：O(1)</li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Medium",
                "Binary Search"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-ValidParentheses/",
            "url": "https://akebee.com/leetcode-ValidParentheses/",
            "title": "LeetCode 獨自升級紀 - [Stack] Valid Parentheses (Easy)",
            "date_published": "2025-08-15T07:54:09.000Z",
            "content_html": "<h2 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h2>\n<p>輸入一個只包含 <code>()[]&#123;&#125;</code> 的字串，判斷它是否為「有效括號」。</p>\n<p>input string 只有在以上狀況才視為 valid:</p>\n<ol>\n<li>每個開括號都有對應的閉括號，且順序正確。</li>\n<li>開括號與閉括號的類型必須相同。</li>\n</ol>\n<p>Example:</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">()</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # True</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">()[]</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">&#123;&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # True</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">(]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # False</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">([)]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">      # False</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">s </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;[]&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">      # True</span></span></code></pre>\n<h2 id=\"stack\"><a class=\"anchor\" href=\"#stack\">#</a> Stack</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> isValid</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> str</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> bool</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        stack </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        close_bracket_key_hashmap </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">)</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> c </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> c </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> close_bracket_key_hashmap</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果 c 在 closeToOpen 中，代表他就是 closing bracket，如果不在就先存到 stack，等之後可能遇到的右括號來配對</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> stack </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">and</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> stack</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> close_bracket_key_hashmap</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">c</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果 stack 內有東西，且 stack 最後一個元素是對應的 open bracket</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                    stack</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">pop</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果是對的就把 stack 最後一個元素 pop 掉，</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                    return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> False</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果第一個就是 closing bracket &#125; 那就不用比了，因為順序一定錯誤，直接 return false</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                stack</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">c</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # (，if c in closeToOpen: 不成立，把 ( append 到 stack 內，那如果一開始遇到 closing bracket &#125; 那就不用比了，因為順序一定錯誤</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">                # print(stack)  # ['(', '[', '&#123;']</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> True</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> if</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> not</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> stack </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">else</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> False</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果 stack 內沒有東西，代表全部都配對成功，回傳 True，否則回傳 False</span></span></code></pre>\n<p>大概的具體執行流程，用 pseudo code 解釋，以 input <code>([])</code> 為例：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 第一個 c '(' 不在 close_bracket_key_hashmap 中，代表是 opening bracket，就直接把他 append 到 stack 中</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> c </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> close_bracket_key_hashmap</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">    ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">  stack</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">  </span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 此時的 stack = [ '(' ]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 再來第二個 c '[' 也不在 close_bracket_key_hashmap 中，代表是 opening bracket，就直接把他 append 到 stack 中</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 此時的 stack = [ '(', '[' ]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 再來第三個 c ')' 在 close_bracket_key_hashmap 中，代表是 closing bracket，接下來要檢查 stack 最後一個元素是否是對應的 opening bracket</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 如果 stack 不是空的，且最後一個 append 進 stack 的元素 [ == 對應的元素 [ 的話</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> stack </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">and</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> stack</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> close_bracket_key_hashmap</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">c</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    stack</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">pop</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 就把最後一個元素 pop 掉</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> False</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果不是對應的元素，就直接 return False</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 此時的 stack = [ '(' ]</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 重複以上流程，直到 s 的所有元素都被處理完</span></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(n)</li>\n<li>SC：O(n)</li>\n</ul>\n<h2 id=\"brute-force\"><a class=\"anchor\" href=\"#brute-force\">#</a> Brute Force</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> isValid</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> str</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> bool</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # Brute Force 判斷 s 是否為空字串</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        while</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">()</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">or</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">or</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">&#123;&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            s </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">replace</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">()</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">replace</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">[]</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">replace</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">&#123;&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 刪除所有配對的括號</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> s </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">==</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"\"</span></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(n^2)</li>\n<li>SC：O(n)</li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Easy",
                "Stack"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-3sum/",
            "url": "https://akebee.com/leetcode-3sum/",
            "title": "LeetCode 獨自升級紀 - [Two Pointer] 3sum (Medium)",
            "date_published": "2025-08-07T07:54:09.000Z",
            "content_html": "<h2 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h2>\n<p>給定一個整數數組 nums，判斷是否存在三個元素 a，b，c，使得 a + b + c = 0？找出所有滿足條件且不重複的三元組。</p>\n<h2 id=\"two-pointer\"><a class=\"anchor\" href=\"#two-pointer\">#</a> Two Pointer</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> threeSum</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> list</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> list</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">list</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # type: ignore</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        result </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        nums</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">sort</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 先排序，方便後續處理</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> enumerate</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 如果 a > 0，後面都不可能有三個數字加起來等於 0</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                break</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            if</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> and</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">            </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  #  跳過重複的「起始值」a，i > 0 是防止出現 nums[0] 跟 nums[-1] 比較，導致比較第一個跟最後一個元素的情況</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">                # a == nums[i - 1] 是避免 不同 index 但 value 一樣 的情況 ex: [-1,-1,...]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                continue</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            l</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> -</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 左右指針</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> l </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                total </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> +</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">r</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> total </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 因為是 sorted 的，代表 nums[r] 太大了</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                    r </span><span style=\"color:#999999;--shiki-dark:#666666\">-=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                elif</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> total </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                    l </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                    result</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">a</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">r</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">                    # [-2,-2,0,0,2,2]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                    l </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">                    # 跳過重複的 left pointer 值</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                    while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> l </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">and</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                        l </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> result</span></span></code></pre>\n<ul>\n<li>TC：O(n^2)</li>\n<li>SC：O(n)</li>\n</ul>\n<p>為何一開始要</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    break</span></span></code></pre>\n<p>因為 nums 是 sorted 的，所以如果當前的 <code>a</code> 已經大於 0，後面的數字只會更大，就不可能再找到符合條件的組合，例如：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">4</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> -</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> -</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 2</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<ul>\n<li>當 a = -4 或 -1，還可能湊出 0</li>\n<li>當 a = 0，還可能有 [0,0,0]</li>\n<li>但當 a = 1 的時候，因為後面全是 1, 2, ...\n<ul>\n<li>最小的總和是 1 + 1 + 2 = 4 &gt; 0。沒必要再檢查，直接結束迴圈</li>\n</ul>\n</li>\n</ul>\n<p>還有為何除了比較下一個值是否與前一個值相同外 <code>a == nums[i - 1]</code>，還要比較 <code>i &gt; 0</code> ？</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">0</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">sort</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">result </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> enumerate</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">   # ❌ 沒有 i>0 保護</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        continue</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    l</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> l </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> r</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        total </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> a </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> +</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">r</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> total </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">==</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            result</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">a</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">l</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">r</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            l </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            r </span><span style=\"color:#999999;--shiki-dark:#666666\">-=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">print</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">result</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span></code></pre>\n<p>假設 <code>i</code> 為 0，則 <code>nums[i - 1]</code> 會變成 <code>nums[-1]</code>，這樣就會比較當第一個元素與最後一個元素</p>\n<p>執行流程</p>\n<ol>\n<li>\n<p>i = 0</p>\n<ul>\n<li>判斷：<code>nums[0] == nums[-1]</code> → 0 == 0 → 成立！</li>\n<li>所以直接 continue 跳過，連雙指針都沒跑。</li>\n<li>第一個合法的 0 被整個跳掉了。</li>\n</ul>\n</li>\n<li>\n<p>i = 1</p>\n<ul>\n<li>判斷：<code>nums[1] == nums[0]</code> → 0 == 0 → 成立，再跳過。</li>\n</ul>\n</li>\n<li>\n<p>i = 2</p>\n<ul>\n<li>判斷：<code>nums[2] == nums[1]</code> → 0 == 0 → 成立，再跳過。</li>\n</ul>\n</li>\n</ol>\n<p>結果變成 <code>result = []</code>，沒找到解，但其實 <code>[0,0,0]</code> 是正解。</p>\n<h2 id=\"brute-force\"><a class=\"anchor\" href=\"#brute-force\">#</a> Brute Force</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> threeSum</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> list</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> list</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">list</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        n </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        results </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> set</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 用 set 避免重複</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">n</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> j </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> n</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> k </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">j </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> n</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">                    if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> +</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">j</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> +</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">k</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                        three_num </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> tuple</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">sorted</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">j</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">k</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">]</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # set 內的元素必須要是能 Hashable的</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                        results</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">add</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">three_num</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # set 轉回 list[list]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">list</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">t</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> t </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> results</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<ul>\n<li>TC：O(n^3)</li>\n<li>SC：O(m)</li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Two Pointer",
                "Medium"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-GroupAnagrams/",
            "url": "https://akebee.com/leetcode-GroupAnagrams/",
            "title": "LeetCode 獨自升級紀 - [Hashmap] Group Anagrams (Medium)",
            "date_published": "2025-08-07T07:54:09.000Z",
            "content_html": "<h1 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h1>\n<p>Example 1:</p>\n<p>Input: strs = [&quot;eat&quot;,&quot;tea&quot;,&quot;tan&quot;,&quot;ate&quot;,&quot;nat&quot;,&quot;bat&quot;]</p>\n<p>Output: [[&quot;bat&quot;],[&quot;nat&quot;,&quot;tan&quot;],[&quot;ate&quot;,&quot;eat&quot;,&quot;tea&quot;]]</p>\n<p>給定一個字串陣列 <code>strs</code>，請將所有「anagram」字串分在同一組。</p>\n<ul>\n<li><code>anagram</code>：相同字母異序詞，指由相同字母組成、順序不同的單字</li>\n<li>例如：<code>&quot;eat&quot;</code>, <code>&quot;tea&quot;</code>, <code>&quot;ate&quot;</code> 是一組。</li>\n</ul>\n<hr />\n<h2 id=\"解題關鍵觀念hashmap-排序後當-key\"><a class=\"anchor\" href=\"#解題關鍵觀念hashmap-排序後當-key\">#</a> 解題關鍵觀念：HashMap + 排序後當 Key</h2>\n<ul>\n<li>任何一組 anagram，<strong>排序後的結果一定一樣</strong>\n<ul>\n<li>比如 <code>&quot;eat&quot;</code> → <code>&quot;aet&quot;</code></li>\n<li><code>&quot;tea&quot;</code> → <code>&quot;aet&quot;</code></li>\n<li><code>&quot;ate&quot;</code> → <code>&quot;aet&quot;</code></li>\n</ul>\n</li>\n<li>所以這樣就可以用排序後的字串當作 <code>key</code>，原始字串加進 <code>value</code> 的 list 裡。</li>\n</ul>\n<hr />\n<h2 id=\"hashmap\"><a class=\"anchor\" href=\"#hashmap\">#</a> Hashmap</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> collections </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> defaultdict</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> typing </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> List</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> groupAnagrams</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> strs</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> List</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">str</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> List</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">List</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">str</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        anagrams_map </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> defaultdict</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">list</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # 先遍歷每個單字，然後去 sorted 他</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> word </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> strs</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            single_sorted_word </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> sorted</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">word</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # sorted 是回傳單個單字的陣列：['a', 'e', 't']...</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            key </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> ''</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">join</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">single_sorted_word</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # aet,ant...</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">            # if key not in anagrams_map: # 用 defaultdict 就不用先檢查 key 是否存在了，不存在會直接幫你預測創建預設值</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">            #     anagrams_map[key] = []</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            anagrams_map</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">key</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">append</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">word</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> list</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">anagrams_map</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">values</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span></code></pre>\n<p>處理過程：</p>\n<table>\n<thead>\n<tr>\n<th>原始字串</th>\n<th>排序後的 key</th>\n<th>儲存狀態</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>eat</td>\n<td>('a', 'e', 't')</td>\n<td>{'aet': ['eat']}</td>\n</tr>\n<tr>\n<td>tea</td>\n<td>('a', 'e', 't')</td>\n<td>{'aet': ['eat', 'tea']}</td>\n</tr>\n<tr>\n<td>tan</td>\n<td>('a', 'n', 't')</td>\n<td>{'ant': ['tan']}</td>\n</tr>\n<tr>\n<td>ate</td>\n<td>('a', 'e', 't')</td>\n<td>{'aet': ['eat', 'tea', 'ate']}</td>\n</tr>\n<tr>\n<td>nat</td>\n<td>('a', 'n', 't')</td>\n<td>{'ant': ['tan', 'nat']}</td>\n</tr>\n<tr>\n<td>bat</td>\n<td>('a', 'b', 't')</td>\n<td>{'abt': ['bat']}</td>\n</tr>\n</tbody>\n</table>\n<ul>\n<li>\n<p>Time Complexity: <code>O(n * k log k)</code></p>\n<ul>\n<li>n 是字串的數量，k 是每個字串的平均長度</li>\n<li>對每個字串進行排序的時間複雜度是 O(k log k)，因此總時間複雜度是 O(n * k log k)</li>\n</ul>\n</li>\n<li>\n<p>Space Complexity: <code>O(n * k)</code></p>\n<ul>\n<li>最壞情況下，所有字串都是不同的 anagram，會產生 <code>n</code> 個不同的 key</li>\n<li>每個 key 對應一個 list，總共要儲存 <code>n</code> 個長度為 <code>k</code> 的字串</li>\n<li>所以總空間為 <code>O(n * k)</code></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"為何要用-defaultdict\"><a class=\"anchor\" href=\"#為何要用-defaultdict\">#</a> 為何要用 defaultdict？</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> collections </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> defaultdict</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">anagrams_map </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> defaultdict</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">list</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span></code></pre>\n<p>當某個 key 不存在時，自動建立對應的空 list。</p>\n<p>不用每次都需要去判斷 HashMap 中是否已經有這個 key，簡化程式碼。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> key </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">not</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> hashmap</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    hashmap</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">key</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Medium",
                "Hashmap"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-TwoSumII_InputArrayIsSorted/",
            "url": "https://akebee.com/leetcode-TwoSumII_InputArrayIsSorted/",
            "title": "LeetCode 獨自升級紀 - [Two Pointer] Two Integer Sum II (Medium)",
            "date_published": "2025-08-07T07:54:09.000Z",
            "content_html": "<h2 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h2>\n<p>給定一個整數陣列 <code>numbers</code> 和一個目標數字 <code>target</code>，請找出 <code>numbers</code> 中兩個數字的和等於 <code>target</code>，並返回它們的索引。假設每個輸入只會有一個解，並且你可以假設每個輸入的數字都是唯一的。</p>\n<h2 id=\"三種解法\"><a class=\"anchor\" href=\"#三種解法\">#</a> 三種解法</h2>\n<p>這題跟 Two Sum 最大的差別是，這邊 INPUT: 的 <code>numbers</code> 陣列是 sorted 的，所以題目會提示我們用更有效率的解法</p>\n<h2 id=\"two-pointers-類型整理\"><a class=\"anchor\" href=\"#two-pointers-類型整理\">#</a> Two Pointers 類型整理</h2>\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">類型</th>\n<th style=\"text-align:left\">移動方式 (關鍵邏輯)</th>\n<th style=\"text-align:left\">核心目的 (解決的問題)</th>\n<th style=\"text-align:left\">範例題</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\"><strong>Opposite Direction</strong></td>\n<td style=\"text-align:left\">兩端向中間<strong>收斂</strong>，每次依條件只動一邊。</td>\n<td style=\"text-align:left\">在<strong>有序數組</strong>中，高效<strong>尋找配對</strong>或<strong>縮小搜索空間</strong>。</td>\n<td style=\"text-align:left\">Two Sum II、Valid Palindrome</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Fast-Slow</strong></td>\n<td style=\"text-align:left\"><strong>不同步</strong>推進（快慢速度），用於拉開距離。</td>\n<td style=\"text-align:left\">檢測鏈表/數組中的<strong>循環</strong>、<strong>尋找中點</strong>，或<strong>原地去重</strong>。</td>\n<td style=\"text-align:left\">Linked List Cycle、Remove Duplicates from Sorted Array</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Sliding Window</strong></td>\n<td style=\"text-align:left\"><strong>擴展右邊界，收縮左邊界</strong>，維持窗口內的條件。</td>\n<td style=\"text-align:left\">搜尋滿足特定條件的<strong>子範圍</strong>（子數組/子字串）的最大/最小問題。</td>\n<td style=\"text-align:left\">Longest Substring Without Repeating Characters、Minimum Size Subarray Sum</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>Two Sequences Sync</strong></td>\n<td style=\"text-align:left\">兩個指標獨立或同步地推進，<strong>同時比較</strong>兩個序列。</td>\n<td style=\"text-align:left\"><strong>合併</strong>或<strong>比對差異</strong>兩個有序的序列。</td>\n<td style=\"text-align:left\">Merge Two Sorted Lists、Compare Version Numbers</td>\n</tr>\n<tr>\n<td style=\"text-align:left\"><strong>K-Distance Window</strong></td>\n<td style=\"text-align:left\">右指標擴展，左指標<strong>受限</strong>於 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mi>K</mi></mrow><annotation encoding=\"application/x-tex\">K</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.07153em;\">K</span></span></span></span> 距離內。</td>\n<td style=\"text-align:left\">判斷<strong>有限距離</strong>內元素是否滿足特定條件。</td>\n<td style=\"text-align:left\">Contains Duplicate II、Longest Repeating Character Replacement</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"兩指針法two-pointer\"><a class=\"anchor\" href=\"#兩指針法two-pointer\">#</a> 兩指針法（Two Pointer）</h2>\n<p>這邊是用 Two Pointer 的對撞指標 (Opposite Direction)，時間複雜度為 O(n)。該方法的思路是將 <code>numbers</code> 陣列排序後，使用兩個指針分別指向陣列的開頭和結尾，然後不斷根據當前指針所指的數字來調整指針的位置。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># Two Pointer</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">left</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> right </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">numbers</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> -</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">while</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> left </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> right</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    total </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> numbers</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">left</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> +</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> numbers</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">right</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> total </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        left </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    elif</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> total </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        right </span><span style=\"color:#999999;--shiki-dark:#666666\">-=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    else</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">left </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> right </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">return</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 沒有結果</span></span>\n<span class=\"line\"></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(n)</li>\n<li>SC：O(1)</li>\n</ul>\n<h2 id=\"brute-force\"><a class=\"anchor\" href=\"#brute-force\">#</a> Brute Force</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># O(n^2) Brute Force</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">numbers</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> j </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">numbers</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> numbers</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">j</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> numbers</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            return</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> j </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">+</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(n^2)</li>\n<li>SC：O(1)</li>\n</ul>\n<h2 id=\"hash-map\"><a class=\"anchor\" href=\"#hash-map\">#</a> Hash Map</h2>\n<p>跟 Two Sum 一樣，使用 Hash Map 儲存已經遍歷過的數字和它們的索引。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># O(n), SC: O(n) Hash map</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">hash_m </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> num </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> enumerate</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">numbers</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> start</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    diff </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> target </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> num</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> diff </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">in</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> hash_m</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">hash_m</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">diff</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    hash_m</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">num</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span></span></code></pre>\n<p>時間與空間複雜度：</p>\n<ul>\n<li>TC：O(n)</li>\n<li>SC：O(n)</li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Two Pointer",
                "Medium"
            ]
        },
        {
            "id": "https://akebee.com/leetcode-RemoveDuplicatesfromSortedArray/",
            "url": "https://akebee.com/leetcode-RemoveDuplicatesfromSortedArray/",
            "title": "LeetCode 獨自升級紀 - [Two Pointer] Remove Duplicates from Sorted Array (Easy)",
            "date_published": "2025-08-07T07:54:09.000Z",
            "content_html": "<h1 id=\"題目\"><a class=\"anchor\" href=\"#題目\">#</a> 題目</h1>\n<p>給定一個非遞減排序（non-decreasing order）的整數 nums 陣列 <code>nums</code>，請 <code>in-place</code> 原地修改原陣列移除所有重複的元素，使每個元素只出現一次，並返回移除重複元素後的陣列長度。</p>\n<p>重點 <code>in-place</code> 就是不能回傳一個新的 List</p>\n<p>此題已有三個要素</p>\n<ul>\n<li>陣列已經排序（Sorted）</li>\n<li>所有重複的元素都會相鄰出現</li>\n<li>保持 O(1) 額外空間</li>\n</ul>\n<p>題目要求：</p>\n<ol>\n<li>原地（in-place）移除重複元素</li>\n<li>不能用額外的陣列來儲存答案</li>\n<li>只能在原陣列 nums 上修改</li>\n<li>每個不重複的元素只保留一次</li>\n<li>保持原本的相對順序</li>\n<li>函式要回傳不重複元素的數量 k</li>\n</ol>\n<p>從上述能看出這題目標就是想讓我們達到 O(n) 的時間複雜度和 O(1) 的空間複雜度。</p>\n<h2 id=\"解法一-set-去重-排序\"><a class=\"anchor\" href=\"#解法一-set-去重-排序\">#</a> 解法一 set 去重 + 排序</h2>\n<p>順便複習一下 python set 的特點</p>\n<ol>\n<li>無序</li>\n<li>不重複</li>\n<li>不支援索引</li>\n<li>mutable（可變）</li>\n</ol>\n<p>那為何這題不能直接使用 <code>set</code> 去重，因為題目要求是 <code>in-place</code> 修改原陣列，<code>list(set(nums))</code> 會建立一個新的 list 物件，並沒有改動原本的 nums。</p>\n<p>題目也希望使用常數額外空間（O(1) space complexity），而 set(nums) 和 list(...) 都會額外建立新的資料結構，額外空間是 O(n)。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 錯誤寫法</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> removeDuplicates</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> List</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        unique_list </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> list</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">set</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span></code></pre>\n<p>題目要求的是在原陣列上直接修改（in-place），讓前 k 個位置變成去重後的內容。</p>\n<p>Custom Judge:</p>\n<p>The judge will test your solution with the following code:</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-java\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">int</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> nums</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#999999;--shiki-dark:#666666\">...</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> // Input array</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">int</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> expectedNums</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#999999;--shiki-dark:#666666\">...</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> // The expected answer with correct length</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">int</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> k</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> removeDuplicates</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> // Calls your implementation</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">assert</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> k </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">==</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> expectedNums</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">length</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">for</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">int</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> i</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">&#x3C;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> k</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> i</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">++</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    assert</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ==</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> expectedNums</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">i</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span></code></pre>\n<p>所以假設還要硬要用 set 來解的話就是，用 <code>set</code> 去重後再排序，然後將結果放回原陣列。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> removeDuplicates</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> List</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        unique_set </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> sorted</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">set</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">unique_set</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> unique_set </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 將 nums 中前 len(unique_num) 長度的陣列替換成 unique_num</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> len</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">unique_set</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span></code></pre>\n<p>但這樣的</p>\n<ul>\n<li>時間複雜度是 <code>O(n log n)</code>，因為排序的時間複雜度是 O(n log n)，不符合題目要求的 O(n)。\n<ul>\n<li>set(nums) → 將陣列轉成集合，去重\n<ul>\n<li>建立集合需要遍歷整個陣列 → O(n)</li>\n</ul>\n</li>\n<li>sorted(...) → 集合轉回排序列表\n<ul>\n<li>排序複雜度 → O(k log k)，k ≤ n</li>\n<li>最壞情況（全部元素不同）就是 O(n log n)</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>空間複雜度是 <code>O(n)</code>，因為建立了新的集合和列表</li>\n</ul>\n<h2 id=\"two-pointer-fast-slow-快慢指標\"><a class=\"anchor\" href=\"#two-pointer-fast-slow-快慢指標\">#</a> Two Pointer Fast-Slow 快慢指標</h2>\n<p>用 Two Pointer 可以 <code>原地修改（in-place）</code></p>\n<ol>\n<li>slow → 放下一個不重複數字的位置，同時能告訴我們現在已經有多少個不重複的數字了 Ex: nums[0,..slow-1]，</li>\n<li>fast → 尋找新數字</li>\n</ol>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> Solution</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">    def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> removeDuplicates</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">self</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> List</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#998418;--shiki-dark:#B8A965\">int</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> -</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#998418;--shiki-dark:#B8A965\"> int</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        slow </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 1</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        for</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> fast </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">in</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> range</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#998418;--shiki-dark:#B8A965\">len</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">nums</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # 從 index 1 開始往右找</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">            if</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">fast</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> !=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">fast</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">-</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # 跟上一個數字比，若不一樣就 update 給 slow pointer</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">slow</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> nums</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">fast</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">                slow </span><span style=\"color:#999999;--shiki-dark:#666666\">+=</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">1</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> slow </span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 不重複元素的數量</span></span></code></pre>\n<h3 id=\"為何從-index-1-開始\"><a class=\"anchor\" href=\"#為何從-index-1-開始\">#</a> 為何從 index 1 開始？</h3>\n<p>因為 INPUT 的陣列是排好序的，所以相同的數字一定會排在一起。第一個數字 nums[0] 前面沒有任何數字可以跟它重複，所以它一定是唯一的。</p>\n<p>所以可以直接保留它，從第二個數字開始檢查，看看有沒有新的不同數字。</p>\n<ul>\n<li>時間複雜度是 <code>O(n)</code>，因為只需要遍歷一次陣列。</li>\n<li>空間複雜度是 <code>O(1)</code>，因為只使用了常數額外空間。</li>\n</ul>\n",
            "tags": [
                "Python",
                "LeetCode",
                "Two Pointer",
                "Easy"
            ]
        },
        {
            "id": "https://akebee.com/django-linebot-2025/",
            "url": "https://akebee.com/django-linebot-2025/",
            "title": "2025 Django 串接 LINE Messaging API 流程",
            "date_published": "2025-07-26T14:46:19.000Z",
            "content_html": "<h2 id=\"1-安裝套件\"><a class=\"anchor\" href=\"#1-安裝套件\">#</a> 1. 安裝套件</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">pip</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> install</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> line-bot-sdk</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> python-decouple</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> python-dotenv</span></span></code></pre>\n<p>記得更新 requirements.txt，在正式主機上部署時記得下載，別像我很笨的卡在回傳 502 error，結果 systemctl 一看才發現是 <code>No module named 'linebot'</code></p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">pip</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> freeze</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> </span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> requirements.txt</span></span></code></pre>\n<h2 id=\"2-line-developer-console-line-official-account-manager-設定\"><a class=\"anchor\" href=\"#2-line-developer-console-line-official-account-manager-設定\">#</a> 2. LINE Developer Console &amp; LINE Official Account Manager 設定</h2>\n<p>前往 <a href=\"https://developers.line.biz/\">LINE Developers Console</a> 建立新的 Provider 或使用現有的</p>\n<p><img loading=\"lazy\" src=\"/images/2025/django-linebot-2025/create_provider.png\" alt=\"create_provider\" /></p>\n<p>Provider 新增完畢後，現在要用 LINE Messaging API，要先去 <a href=\"https://manager.line.biz/\">LINE Official Account Manager</a> 建立官方帳號然後啟用 Messaging API，已經無法在 LINE Developers Console 直接啟用 Messaging API 了</p>\n<p><img loading=\"lazy\" src=\"/images/2025/django-linebot-2025/manager_setting.png\" alt=\"manager_setting\" /></p>\n<p><img loading=\"lazy\" src=\"/images/2025/django-linebot-2025/manager_message_api.png\" alt=\"manager_message_api\" /></p>\n<p>然後回到 LINE Developers Console，記錄以下資訊：</p>\n<ul>\n<li>Channel Secret</li>\n<li>Channel Access Token</li>\n</ul>\n<h2 id=\"3-設定-line-的-webhook-url\"><a class=\"anchor\" href=\"#3-設定-line-的-webhook-url\">#</a> 3. 設定 LINE 的 Webhook URL</h2>\n<p>這邊是等等會再後端的 <code>urls.py</code>自己命名的 url，我的是 <code>https://yourdomain.com/api/line-bot/webhook/</code></p>\n<h2 id=\"django-後端\"><a class=\"anchor\" href=\"#django-後端\">#</a> Django 後端</h2>\n<p>建立 line_bot apps</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">cd</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> backend/</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">python</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> manage.py</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> startapp</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> line_bot</span></span></code></pre>\n<p>新增 LINE Bot App 到 INSTALLED_APPS</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># backend/crm_backend/settings.py</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">INSTALLED_APPS</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">django.contrib.admin</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">    ...</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # Local apps</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">customers</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">line_bot</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 新增 LINE Bot app</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<h2 id=\"設定-allowed_hosts\"><a class=\"anchor\" href=\"#設定-allowed_hosts\">#</a> 設定 ALLOWED_HOSTS</h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># backend/crm_backend/settings.py</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">ALLOWED_HOSTS</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">yourdomain.com</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">localhost</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">127.0.0.1</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<h2 id=\"建立-appspy\"><a class=\"anchor\" href=\"#建立-appspy\">#</a> 建立 <code>apps.py</code></h2>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># backend/line_bot/apps.py</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> django</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">apps </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> AppConfig</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">class</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\"> LineBotConfig</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#59873A;--shiki-dark:#80A665\">AppConfig</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    default_auto_field </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">django.db.models.BigAutoField</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    name </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">line_bot</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span></span></code></pre>\n<h2 id=\"webhook-串接測試\"><a class=\"anchor\" href=\"#webhook-串接測試\">#</a> Webhook 串接測試</h2>\n<h3 id=\"views\"><a class=\"anchor\" href=\"#views\">#</a> views</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># backend/line_bot/views.py</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 載入專案根目錄的 .env 檔案</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">PROJECT_ROOT</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> Path</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">__file__</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">resolve</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">parent</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">parent</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">parent</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">ENV_FILE</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> PROJECT_ROOT</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> /</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">.env</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">load_dotenv</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">ENV_FILE</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> linebot </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> LineBotApi</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> WebhookHandler</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> linebot</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">exceptions </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> InvalidSignatureError</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> LineBotApiError</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> linebot</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">models </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> MessageEvent</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> TextMessage</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> TextSendMessage</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 設置日誌</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">logger </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> logging</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">getLogger</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#998418;--shiki-dark:#B8A965\">__name__</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># LINE Bot 設定 - 從環境變數讀取</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">LINE_CHANNEL_SECRET</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> os</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">getenv</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">LINE_CHANNEL_SECRET</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">LINE_CHANNEL_ACCESS_TOKEN</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> os</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">getenv</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">LINE_CHANNEL_ACCESS_TOKEN</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 驗證環境變數是否存在</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">if</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> not</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> LINE_CHANNEL_SECRET</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> or</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> not</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> LINE_CHANNEL_ACCESS_TOKEN</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">error</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">LINE Bot 環境變數未設置！請檢查 .env 檔案</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    raise</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> ValueError</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">LINE Bot 環境變數未設置</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">line_bot_api </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> LineBotApi</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">LINE_CHANNEL_ACCESS_TOKEN</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">handler </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> WebhookHandler</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">LINE_CHANNEL_SECRET</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">@</span><span style=\"color:#59873A;--shiki-dark:#80A665\">csrf_exempt</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">@</span><span style=\"color:#59873A;--shiki-dark:#80A665\">require_POST</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> webhook</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">request</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">LINE Bot Webhook 處理器</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 獲取請求內容</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    body </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> request</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">body</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">decode</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">utf-8</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    signature </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> request</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">META</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">get</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">HTTP_X_LINE_SIGNATURE</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> ''</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 記錄接收到的請求</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">info</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"收到 LINE Webhook 請求: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">body</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">info</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Signature: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">signature</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    try</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # 驗證簽名並處理事件</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        handler</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">handle</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">body</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> signature</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    except</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> InvalidSignatureError</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">error</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">無效的簽名</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> HttpResponseBadRequest</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">Invalid signature</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    except</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> Exception</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> as</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> e</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">error</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"處理 Webhook 時發生錯誤: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#998418;--shiki-dark:#B8A965\">str</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">e</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">        return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> HttpResponseBadRequest</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"Error: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#998418;--shiki-dark:#B8A965\">str</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">e</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> HttpResponse</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">OK</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">@</span><span style=\"color:#59873A;--shiki-dark:#80A665\">handler</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">add</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">MessageEvent</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> message</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">TextMessage</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> handle_text_message</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">event</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">處理文字訊息</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    user_id </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> event</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">source</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">user_id</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    user_message </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> event</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">message</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">text</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 記錄收到的訊息</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">info</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"收到使用者 </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">user_id</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 的訊息: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">user_message</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 簡單的回應邏輯</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    reply_message </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"您好！我收到了您的訊息：「</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">user_message</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">」\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    try</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # 回覆訊息</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        line_bot_api</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">reply_message</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            event</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">reply_token</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">            TextSendMessage</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">text</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">reply_message</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">        </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">info</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"成功回覆訊息給使用者 </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">user_id</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    except</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> LineBotApiError </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">as</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> e</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">        logger</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">error</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">f</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"回覆訊息時發生錯誤: </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">e</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">message</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">def</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> test_connection</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">request</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">    \"\"\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">測試連接的簡單端點</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    return</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> HttpResponse</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">LINE Bot 服務運行中！</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span></code></pre>\n<p>LINE_CHANNEL_SECRET 跟 LINE_CHANNEL_ACCESS_TOKEN 可直接在 LINE Developers Console 的 Basic settings 跟 Messaging API 頁面找到</p>\n<p><img loading=\"lazy\" src=\"/images/2025/django-linebot-2025/token.png\" alt=\"token\" /></p>\n<h3 id=\"url-配置\"><a class=\"anchor\" href=\"#url-配置\">#</a> url 配置</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># backend/line_bot/urls.py</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> django</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">urls </span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> path</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">from</span><span style=\"color:#999999;--shiki-dark:#666666\"> .</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> import</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> views</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">app_name </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> '</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">line_bot</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">urlpatterns </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    path</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">webhook/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> views</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">webhook</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> name</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">webhook</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # LINE Bot Webhook 處理器</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    path</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">test/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> views</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">test_connection</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> name</span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">test</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 最基本的連接測試</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span></code></pre>\n<h3 id=\"更新專案主-urls-配置\"><a class=\"anchor\" href=\"#更新專案主-urls-配置\">#</a> 更新專案主 urls 配置</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># backend/crm_backend/urls.py</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">urlpatterns </span><span style=\"color:#999999;--shiki-dark:#666666\">=</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">[</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    path</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">admin/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> admin</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">site</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">urls</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">    ...</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">    path</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">api/line-bot/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> include</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">line_bot.urls</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">  # 新增 LINE Bot 路由</span></span>\n<span class=\"line\"><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">]</span></span>\n<span class=\"line\"></span></code></pre>\n<p>接下來可以直接去 Terminal 測試以下 API，若成功的話，應會在 Terminal 中看到：「LINE Bot 服務運行中！」 的回應</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 測試基本連接</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">curl</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> https://yourdomain.com/api/line-bot/test/</span></span></code></pre>\n<p>基本的測試過了，接下來就可以測帶有 body 跟 signature 的 Webhook 處理器測試，最後部署至正式環境再用真實的 webhook url 測試，基本上正式的過了，就可以直接到 LINE 去傳訊息看看是否有成功回覆 handle_text_message 的 reply_message：</p>\n<p>成功畫面：</p>\n<p><img loading=\"lazy\" src=\"/images/2025/django-linebot-2025/reply_success.png\" alt=\"reply_success\" /></p>\n<h3 id=\"講解一下-執行流程\"><a class=\"anchor\" href=\"#講解一下-執行流程\">#</a> 講解一下 執行流程</h3>\n<ol>\n<li>當我們在 LINE 上傳送訊息時， LINE 平台會發送 HTTP POST 到 webhook URL</li>\n<li>Django <code>urls.py</code> 會將請求路由到 <code>webhook</code> 函數</li>\n<li>webhook() 驗證簽名(確保來自 LINE)後，呼叫 handler.handle()，將請求交給 handler.handle() 處理</li>\n<li>handler 解析事件，發現是 TextMessage</li>\n<li>自動調用 handle_text_message() 函數</li>\n<li>handle_text_message() 生成回應並透過 LINE API 回覆</li>\n</ol>\n<h2 id=\"常見問題\"><a class=\"anchor\" href=\"#常見問題\">#</a> 常見問題</h2>\n<h3 id=\"1-301-moved-permanently-錯誤\"><a class=\"anchor\" href=\"#1-301-moved-permanently-錯誤\">#</a> 1. 301 Moved Permanently 錯誤</h3>\n<p><strong>問題</strong>: HTTP 請求被重定向到 HTTPS<br />\n<strong>解決</strong>: 確保使用 HTTPS URL 進行所有請求</p>\n<h3 id=\"2-invalid-signature-錯誤\"><a class=\"anchor\" href=\"#2-invalid-signature-錯誤\">#</a> 2. Invalid Signature 錯誤</h3>\n<p><strong>問題</strong>: Channel Secret 不正確或環境變數未正確載入<br />\n<strong>解決</strong>:</p>\n<ul>\n<li>檢查 <code>.env</code> 檔案路徑</li>\n<li>確認 Channel Secret 正確</li>\n</ul>\n<h3 id=\"3-line-bot-無回應\"><a class=\"anchor\" href=\"#3-line-bot-無回應\">#</a> 3. LINE Bot 無回應</h3>\n<p><strong>問題</strong>: Webhook 設定錯誤或程式碼異常<br />\n<strong>解決</strong>:</p>\n<ul>\n<li>檢查 Django logs</li>\n<li>確認 Webhook URL 正確</li>\n<li>測試 <code>/api/line-bot/test/</code> 端點</li>\n</ul>\n<h3 id=\"4-csrf-錯誤\"><a class=\"anchor\" href=\"#4-csrf-錯誤\">#</a> 4. CSRF 錯誤</h3>\n<p><strong>問題</strong>: Django CSRF 保護阻擋 LINE Webhook<br />\n<strong>解決</strong>: 使用 <code>@csrf_exempt</code> 裝飾器（已包含在範例中）</p>\n",
            "tags": [
                "Django",
                "LINE BOT"
            ]
        },
        {
            "id": "https://akebee.com/react-usecallback/",
            "url": "https://akebee.com/react-usecallback/",
            "title": "React useCallback 如何解決 useEffect 無限循環問題",
            "date_published": "2025-07-21T14:31:05.000Z",
            "content_html": "<h2 id=\"問題\"><a class=\"anchor\" href=\"#問題\">#</a> 問題</h2>\n<p>直接先看問題，原本照下方程式碼這樣子去調用 <code>fetchCustomerData()</code> 的話，會出現 <code>React Hook useEffect has a missing dependency: 'fetchCustomerData'. Either include it or remove the dependency array.</code> 的警告訊息</p>\n<p><img loading=\"lazy\" src=\"/images/2025/react-usecallback/useffect-error.png\" alt=\"useffect-error\" /></p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-js\"><span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">...</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  useEffect</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    fetchCustomerData</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">  </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">customerId</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">  const</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> fetchCustomerData</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> async</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#1E754F;--shiki-dark:#4D9375\">    try</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      setLoading</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">true</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">      const</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">[</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">customerResponse</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> ordersResponse</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> transactionsResponse</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">]</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> await</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> Promise</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">all</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a13865;--shiki-dark:#d9739f\">[</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">        api</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">get</span><span style=\"color:#999999;--shiki-dark:#666666\">&#x3C;</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">Customer</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">`</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/customers/</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">$&#123;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">customerId</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">`</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">        api</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">get</span><span style=\"color:#999999;--shiki-dark:#666666\">&#x3C;</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">Order</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">[</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">`</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/customers/</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">$&#123;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">customerId</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/orders/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">`</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">        api</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">get</span><span style=\"color:#999999;--shiki-dark:#666666\">&#x3C;</span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">Transaction</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">[</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">]</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">`</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/customers/</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">$&#123;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">customerId</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">&#125;</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/transactions/</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">`</span><span style=\"color:#bda437;--shiki-dark:#e6cc77\">)</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">      </span><span style=\"color:#a13865;--shiki-dark:#d9739f\">]</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      setCustomer</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">customerResponse</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">data</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      setOrders</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">ordersResponse</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">data</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      setTransactions</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">transactionsResponse</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">data</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> catch</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">err</span><span style=\"color:#999999;--shiki-dark:#666666\">: </span><span style=\"color:#2E8F82;--shiki-dark:#5DA994\">unknown</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      setError</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">無法取得客戶資料</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">      console</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">error</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">Error fetching customer:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> err</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> finally</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      setLoading</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\">false</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">  </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">...</span></span></code></pre>\n<p>如果不去理解的話，就會以為只要單純把 <code>fetchCustomerData</code> 放進 useEffect 的依賴陣列，並且將 <code>fetchCustomerData</code> 函數放在 useEffect 之前定義就可以解決問題</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-js\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> const</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> fetchCustomerData</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> async</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">  console</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">log</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">fetchCustomerData called</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">        ...</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\"> useEffect</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  fetchCustomerData</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">customerId</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> fetchCustomerData</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span></code></pre>\n<p>當然很快的你就會發現 IDE 就會直接警告你會觸發無限 re-render 了，因為每次 <code>fetchCustomerData</code> 被調用時都會重新創建一個新的函式，又會觸發 <code>useEffect</code> 的依賴檢查，導致 <code>useEffect</code> 無限循環</p>\n<p><img loading=\"lazy\" src=\"/images/2025/react-usecallback/unlimit-rerender.png\" alt=\"unlimit-rerender\" /></p>\n<p>原因：</p>\n<ol>\n<li>第一次渲染：\n<ul>\n<li>創建 <code>fetchCustomerData</code> 函數 (函數引用 = function#1)</li>\n<li>useEffect 執行，依賴是 [customerId: 123, function#1]</li>\n</ul>\n</li>\n<li>useEffect 執行後觸發重新渲染：\n<ul>\n<li>重新創建 <code>fetchCustomerData</code> 函數 (函數引用 = function#2)</li>\n<li>React 比較依賴：[customerId: 123, function#2] ≠ [customerId: 123, function#1] - 因為函數引用不同，觸發 useEffect</li>\n</ul>\n</li>\n<li>無限循環開始：\n<ul>\n<li>渲染 → 新函數 → useEffect 觸發 → 重新渲染 → 新函數 → useEffect 觸發 → ...</li>\n</ul>\n</li>\n</ol>\n<h2 id=\"解決方法\"><a class=\"anchor\" href=\"#解決方法\">#</a> 解決方法</h2>\n<p>使用 <code>useCallback</code> 包裝 <code>fetchCustomerData</code> 函數，並且指定 <code>customerId</code> 為依賴，這樣可以確保只有當 <code>customerId</code> 改變時才會重新創建函數，從而避免無限循環的問題。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-js\"><span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">  const</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> fetchCustomerData</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> useCallback</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">async</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    ...</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">  </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">customerId</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> // 只有當 customerId 改變時才重新建立函數</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  useEffect</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">(</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#123;</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    fetchCustomerData</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">(</span><span style=\"color:#a65e2b;--shiki-dark:#d4976c\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">  </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">[</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">customerId</span><span style=\"color:#999999;--shiki-dark:#666666\">,</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> fetchCustomerData</span><span style=\"color:#1e754f;--shiki-dark:#4d9375\">]</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span></code></pre>\n<h2 id=\"補充\"><a class=\"anchor\" href=\"#補充\">#</a> 補充</h2>\n<p>這也是技術面試中常會遇到的問題，比如會問 React 是如何判斷何時該重新渲染組件的，或是應該如何避免不必要的渲染等問題</p>\n<p>這是就要好好複習基本觀念也就是 React 並不會深度比較物件或陣列的內容，而是只檢查它們的「記憶體位置」（也就是參考位址）有沒有改變。如果兩個物件內容一樣，但記憶體位置不同，React 會認為它們不一樣；反之，如果直接修改原本的物件內容而沒創造新物件，React 則會以為資料沒變，導致畫面不更新。</p>\n<p>這也是為什麼在 React 中強調 「不可變資料（immutable data）」的原則 —— 只要資料變更，就應該建立一個新物件或新陣列，這樣 React 才能正確判斷有變化並重新渲染畫面。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-js\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">// 每次渲染</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">const</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> func1</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">const</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> func2</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\"> =</span><span style=\"color:#999999;--shiki-dark:#666666\">></span><span style=\"color:#999999;--shiki-dark:#666666\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">console</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">log</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\">func1</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ===</span><span style=\"color:#B07D48;--shiki-dark:#BD976A\"> func2</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> // false! 即使函數內容相同</span></span>\n<span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">console</span><span style=\"color:#999999;--shiki-dark:#666666\">.</span><span style=\"color:#59873A;--shiki-dark:#80A665\">log</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">123</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ===</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 123</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> // true (customerId</span></span></code></pre>\n<p>同樣概念也適用 useEffect 或 useCallback 等 Hook 的 依賴陣列中。如果你在每次渲染時都產生一個新的函式或物件，React 就會認為依賴變了，進而導致 effect 重新執行或元件重新渲染。因此正確管理依賴與避免不必要的重新建立，是提升效能的關鍵。</p>\n",
            "tags": [
                "React",
                "TypeScript"
            ]
        },
        {
            "id": "https://akebee.com/gitops-image-automation/",
            "url": "https://akebee.com/gitops-image-automation/",
            "title": "(二) GitOps 實戰：FluxCD Image Automation 實戰",
            "date_published": "2025-05-13T12:30:00.000Z",
            "content_html": "<blockquote>\n<p><strong>GitOps 實戰系列 (二)</strong><br />\n接續 <strong>(一)</strong> <a href=\"https://akebee.com/gitops-kustomize-fluxcd-gpu-k8s/\">GitOps 實戰：用 Kustomize + FluxCD 管 GPU K8s 叢集</a>。<br />\n上一篇講「Git 是真相、Flux 自動同步」這個大框架；這篇專講其中<strong>最不直覺、最容易踩坑</strong>的一塊：<strong>Image 自動更新</strong>——也就是「我 push 一個新 image 到 Harbor，cluster 自己升版」這件事，背後到底有哪些 Controller、哪些 yaml、會在哪些時間點出問題。</p>\n</blockquote>\n<hr />\n<h2 id=\"tldr\"><a class=\"anchor\" href=\"#tldr\">#</a> TL;DR</h2>\n<ul>\n<li><strong>Image 自動更新由 4 種 Flux 物件協作</strong>：<code>ImageRepository</code>（掃 Harbor）→ <code>ImagePolicy</code>（算最新 tag）→ <code>ImageUpdateAutomation</code>（改 Git）→ 一般 Flux 流程（再從 Git 套回 cluster）。</li>\n<li><strong>不是「Flux 直接改 cluster」</strong>，而是「Flux 改 Git，再讓 Git 同步進 cluster」——這條規律不能繞，因為 <strong>Git 永遠是 source of truth</strong>。</li>\n<li><strong>deployment.yaml 上的 <code>&#123;&quot;$imagepolicy&quot;: &quot;...&quot;&#125;</code> 註解才是 Flux 改寫位置的「錨點」</strong>。沒這行 Flux 不會動 yaml。</li>\n<li><strong><code>filterTags.pattern</code> 是用來「先濾掉雜訊、再算 semver」</strong>，否則 <code>streamlit-0.12.1</code> 這種 prefix tag 會被當成非 semver 丟掉。</li>\n<li><strong>CI 打 tag 用 <code>git describe --tags --abbrev=0</code></strong>——如果你 <code>git tag 0.13.3</code> 推出去後沒推 branch，或 tag 指錯 commit，build 出來的 image tag 就會錯。</li>\n</ul>\n<hr />\n<h2 id=\"一-起點為什麼要image-自動更新\"><a class=\"anchor\" href=\"#一-起點為什麼要image-自動更新\">#</a> 一、起點：為什麼要「Image 自動更新」</h2>\n<h3 id=\"沒有它的痛\"><a class=\"anchor\" href=\"#沒有它的痛\">#</a> 沒有它的痛</h3>\n<p>GitOps 的核心信念是「<strong>Git 是 cluster 的鏡像</strong>」。但 image tag 是個尷尬例外：</p>\n<ul>\n<li>改 <code>replicas</code>、改 <code>env</code>、改 <code>resources</code>——這些<strong>是開發者的意圖</strong>，當然要 commit。</li>\n<li>改 image tag 從 <code>0.13.2</code> 到 <code>0.13.3</code>——這<strong>完全是機械化的事</strong>，CI build 出新 image、k8s 要拉新版，中間哪有什麼「意圖」需要人類在 PR 上 review？</li>\n</ul>\n<p>如果不自動化，流程會變成：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>1. 工程師 PR merge 進 staging</span></span>\n<span class=\"line\"><span>2. CI 跑 push-to-harbor、image 0.13.3 進 Harbor</span></span>\n<span class=\"line\"><span>3. 工程師「再開一個 PR」改 gpu-k8s-devops 上的 deployment.yaml 的 :0.13.2 → :0.13.3</span></span>\n<span class=\"line\"><span>4. review、approve、merge</span></span>\n<span class=\"line\"><span>5. Flux 同步、pod 更新</span></span></code></pre>\n<p>第 3-4 步是純粹的 toil。<strong>Image Automation 把這兩步交給 Flux 自己</strong>。</p>\n<h3 id=\"加上它之後\"><a class=\"anchor\" href=\"#加上它之後\">#</a> 加上它之後</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>1. 工程師 PR merge 進 staging</span></span>\n<span class=\"line\"><span>2. CI push image 0.13.3 進 Harbor</span></span>\n<span class=\"line\"><span>3. Flux 看到新 image → 自動改 deployment.yaml → 自動 commit → 自動 apply</span></span>\n<span class=\"line\"><span>4. Pod 滾動更新</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>(工程師沒做任何手動操作)</span></span></code></pre>\n<p><img loading=\"lazy\" src=\"../images/2026/gitops-fluxcd-pipeline.jpg\" alt=\"End-to-End GitOps Pipeline (Git, Harbor, Flux)\" /></p>\n<hr />\n<h2 id=\"二-四個物件-四件事\"><a class=\"anchor\" href=\"#二-四個物件-四件事\">#</a> 二、四個物件、四件事</h2>\n<p>整條 pipeline 由四個 CRD 接起來，每一個對應一件具體的事：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>┌────────────────────────────────────────────────────────────────────────┐</span></span>\n<span class=\"line\"><span>│                                                                        │</span></span>\n<span class=\"line\"><span>│   Harbor (image registry)                                              │</span></span>\n<span class=\"line\"><span>│       │                                                                │</span></span>\n<span class=\"line\"><span>│       │ 「Harbor 上有哪些 tag？」                                       │</span></span>\n<span class=\"line\"><span>│       ↓                                                                │</span></span>\n<span class=\"line\"><span>│   ┌──────────────────┐                                                 │</span></span>\n<span class=\"line\"><span>│   │ ImageRepository  │  每 5 分鐘掃 Harbor                            │</span></span>\n<span class=\"line\"><span>│   │                  │  → 把 tag 列表存在 status                       │</span></span>\n<span class=\"line\"><span>│   └─────────┬────────┘                                                 │</span></span>\n<span class=\"line\"><span>│             │                                                          │</span></span>\n<span class=\"line\"><span>│             │ 「這堆 tag 裡哪個是最新？」                              │</span></span>\n<span class=\"line\"><span>│             ↓                                                          │</span></span>\n<span class=\"line\"><span>│   ┌──────────────────┐                                                 │</span></span>\n<span class=\"line\"><span>│   │ ImagePolicy      │  套 semver/filter 規則                         │</span></span>\n<span class=\"line\"><span>│   │                  │  → 算出 latestRef.tag                          │</span></span>\n<span class=\"line\"><span>│   └─────────┬────────┘                                                 │</span></span>\n<span class=\"line\"><span>│             │                                                          │</span></span>\n<span class=\"line\"><span>│             │ 「最新版跟 deployment.yaml 上寫的不一樣，改它」          │</span></span>\n<span class=\"line\"><span>│             ↓                                                          │</span></span>\n<span class=\"line\"><span>│   ┌────────────────────┐                                               │</span></span>\n<span class=\"line\"><span>│   │ ImageUpdate         │  每 1 分鐘輪詢                              │</span></span>\n<span class=\"line\"><span>│   │ Automation         │  → 改 yaml、commit、push 回 Git              │</span></span>\n<span class=\"line\"><span>│   └─────────┬──────────┘                                               │</span></span>\n<span class=\"line\"><span>│             │                                                          │</span></span>\n<span class=\"line\"><span>│             │ 「Git 上 yaml 改了」                                     │</span></span>\n<span class=\"line\"><span>│             ↓                                                          │</span></span>\n<span class=\"line\"><span>│   ┌──────────────────┐                                                 │</span></span>\n<span class=\"line\"><span>│   │ GitRepository +  │  (上一篇講過的)                                 │</span></span>\n<span class=\"line\"><span>│   │ Kustomization    │  每 5/10 分鐘 reconcile                        │</span></span>\n<span class=\"line\"><span>│   └─────────┬────────┘                                                 │</span></span>\n<span class=\"line\"><span>│             │                                                          │</span></span>\n<span class=\"line\"><span>│             ↓                                                          │</span></span>\n<span class=\"line\"><span>│        Deployment                                                      │</span></span>\n<span class=\"line\"><span>│        滾動更新                                                        │</span></span>\n<span class=\"line\"><span>│                                                                        │</span></span>\n<span class=\"line\"><span>└────────────────────────────────────────────────────────────────────────┘</span></span></code></pre>\n<p>注意 <strong>GitRepository / Kustomization 不是 Image Automation 的一部分</strong>，是上一篇講過的「<strong>Flux 把 Git 同步進 cluster</strong>」的基本流程，這裡只是接受了 Image Automation 改完的 commit。</p>\n<p>也就是說：<strong>Image Automation 沒有「直接改 cluster」這條路</strong>，它一定走 Git。這個設計刻意保留了「<strong>所有變更都有 git commit 可追</strong>」的 audit 性質。</p>\n<hr />\n<h2 id=\"三-imagerepository告訴-flux看哪個-harbor-repo\"><a class=\"anchor\" href=\"#三-imagerepository告訴-flux看哪個-harbor-repo\">#</a> 三、ImageRepository：告訴 Flux「看哪個 Harbor repo」</h2>\n<p>最簡單的一塊。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># clusters/stage/flux/registry.yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">apiVersion</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image.toolkit.fluxcd.io/v1</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ImageRepository</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">metadata</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  namespace</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">spec</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  image</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> harbor.thebarkingdog.tw/tbd/keyword_correction</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  interval</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 5m0s</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  secretRef</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> harbor-credentials</span></span></code></pre>\n<p>要點：</p>\n<ul>\n<li><code>image</code> 是 <strong>registry 路徑（不含 tag）</strong>——這物件存在的目的就是去 list 這個 repo 下的所有 tag。</li>\n<li><code>interval: 5m</code> 是輪詢間隔。為什麼是 5 分鐘？因為頻率太高 Harbor 會被打爆，太低又延遲——5 分鐘是 Flux 文件建議的合理預設。</li>\n<li><code>secretRef</code> 指向 Harbor 拉取認證（<strong>讀</strong>權限即可，不需要寫權限）。</li>\n</ul>\n<h3 id=\"想加速走-webhook\"><a class=\"anchor\" href=\"#想加速走-webhook\">#</a> 想加速？走 webhook</h3>\n<p>Harbor 推完 image 後也可以打 webhook 通知 Flux 立刻去 re-scan，不用等 5 分鐘輪詢：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># clusters/stage/flux/receiver.yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">apiVersion</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> notification.toolkit.fluxcd.io/v1</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Receiver</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">metadata</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> harbor-receiver</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  namespace</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">spec</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  type</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> generic</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  secretRef</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> webhook-token</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  resources</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">    -</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ImageRepository</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # ... 其他 ImageRepository</span></span></code></pre>\n<p>Notification controller 會給你一個 webhook URL（<code>/hook/&lt;hash&gt;</code>），在 Harbor 那邊設成 push notification target 即可。<strong>但 webhook 路徑常常被防火牆/Ingress 設定卡到</strong>——這時候 5 分鐘輪詢就是 fallback。</p>\n<h3 id=\"確認-flux-看到-tag-了沒\"><a class=\"anchor\" href=\"#確認-flux-看到-tag-了沒\">#</a> 確認 Flux 看到 tag 了沒？</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagerepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">...</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">status:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  lastScanResult:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    tagCount:</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 7</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    scanTime:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">2026-05-13T07:06:14Z</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  ...</span></span></code></pre>\n<p><code>tagCount</code> 跟 Harbor 上的數字對得起來就 OK。看不到？檢查 <code>secretRef</code> 的權限。</p>\n<hr />\n<h2 id=\"四-imagepolicy算出最新版是哪個-tag\"><a class=\"anchor\" href=\"#四-imagepolicy算出最新版是哪個-tag\">#</a> 四、ImagePolicy：算出「最新版」是哪個 tag</h2>\n<p>這是整套裡<strong>最容易設定錯</strong>的一個。<code>ImageRepository</code> 給你一堆 tag，<code>ImagePolicy</code> 的工作是「<strong>從這堆 tag 裡，按某種規則算出唯一的『最新』</strong>」。</p>\n<h3 id=\"純-semver-的情況\"><a class=\"anchor\" href=\"#純-semver-的情況\">#</a> 純 semver 的情況</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">apiVersion</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image.toolkit.fluxcd.io/v1</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ImagePolicy</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">metadata</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> realtime-asr</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  namespace</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">spec</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  imageRepositoryRef</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> realtime-asr</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  policy</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    semver</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      range</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">>=0.0.0</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span></code></pre>\n<p><code>&gt;=0.0.0</code> 的意思是「<strong>所有合法的 semver tag 我都接受，從裡面挑最大的</strong>」。tag 列表 <code>1.2.0, 1.2.1, 1.3.0</code> → latestRef = <code>1.3.0</code>。</p>\n<p>但前提是 <strong>tag 全部都是純 semver</strong>。一旦你有 <code>latest</code>、<code>main-abc123</code>、<code>api-0.12.4</code> 這種非標準 tag，Flux 會直接<strong>忽略</strong>它們（不報錯，只是當沒看到）。</p>\n<h3 id=\"filter-pattern先過濾再-semver\"><a class=\"anchor\" href=\"#filter-pattern先過濾再-semver\">#</a> Filter pattern：先過濾，再 semver</h3>\n<p>實務上專案常有 prefix tag：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>Harbor 上 keyword_correction 的 tag：</span></span>\n<span class=\"line\"><span>  0.13.3         (新格式：合一 image)</span></span>\n<span class=\"line\"><span>  0.13.2</span></span>\n<span class=\"line\"><span>  api-0.13.0     (舊格式：分開兩個 image)</span></span>\n<span class=\"line\"><span>  streamlit-0.13.0</span></span>\n<span class=\"line\"><span>  api-0.12.4</span></span></code></pre>\n<p>直接用 <code>&gt;=0.0.0</code> 的話，<code>api-0.13.0</code> 不是合法 semver 會被丟掉。要納入考量得用 <code>filterTags</code>：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">apiVersion</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image.toolkit.fluxcd.io/v1</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ImagePolicy</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">metadata</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  namespace</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">spec</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  imageRepositoryRef</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  filterTags</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 過渡期相容舊 tag（api-X.Y.Z, streamlit-X.Y.Z），穩定後可移除</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    pattern</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">^(?:api-|streamlit-)?(?P&#x3C;version></span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\\\</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">d+</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\\\</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\\\</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">d+</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\\\</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">.</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\">\\\\</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">d+.*)$</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    extract</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">$version</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  policy</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    semver</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      range</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">>=0.0.0</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span></code></pre>\n<p>兩個欄位的意義：</p>\n<ul>\n<li><strong><code>pattern</code></strong>：先用 regex 過濾。匹配不到的 tag 完全丟掉（例如 <code>latest</code>、<code>sha-abc123</code>）。</li>\n<li><strong><code>extract</code></strong>：從匹配到的 tag 提取「<strong>可以拿去比 semver 的字串</strong>」。<code>(?P&lt;version&gt;...)</code> 是 named group，<code>$version</code> 就是拿那個 group。</li>\n</ul>\n<p>於是：</p>\n<table>\n<thead>\n<tr>\n<th>原始 tag</th>\n<th>匹配？</th>\n<th>extract 結果</th>\n<th>套進 semver</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>0.13.3</code></td>\n<td>✅</td>\n<td><code>0.13.3</code></td>\n<td>0.13.3</td>\n</tr>\n<tr>\n<td><code>api-0.12.4</code></td>\n<td>✅</td>\n<td><code>0.12.4</code></td>\n<td>0.12.4</td>\n</tr>\n<tr>\n<td><code>streamlit-0.13.0</code></td>\n<td>✅</td>\n<td><code>0.13.0</code></td>\n<td>0.13.0</td>\n</tr>\n<tr>\n<td><code>latest</code></td>\n<td>❌</td>\n<td>(略過)</td>\n<td>-</td>\n</tr>\n<tr>\n<td><code>dev-abc</code></td>\n<td>❌</td>\n<td>(略過)</td>\n<td>-</td>\n</tr>\n</tbody>\n</table>\n<p>最後算出 latestRef = <code>0.13.3</code>。</p>\n<h3 id=\"common-gotchas\"><a class=\"anchor\" href=\"#common-gotchas\">#</a> Common gotchas</h3>\n<p><strong>Gotcha 1：regex 跳脫</strong></p>\n<p>yaml 字串裡 <code>\\d</code> 要寫成 <code>\\\\d</code>，否則 YAML parser 會吃掉一個 backslash。看到 <code>pattern: &quot;^\\d+&quot;</code> 不工作不用懷疑，就是這個。</p>\n<p><strong>Gotcha 2：相容舊 tag 的責任</strong></p>\n<p>「過渡期相容」很容易變「永遠相容」。一旦 Harbor 還有舊格式 <code>api-X.Y.Z</code> 沒清掉，pattern 就要一直留 <code>(?:api-|streamlit-)?</code> 這個 optional group——多一個歷史包袱。</p>\n<p><strong>Gotcha 3：semver <code>range</code> 寫法</strong></p>\n<ul>\n<li><code>&gt;=0.0.0</code> ← 全部接受</li>\n<li><code>^1.0.0</code> ← 接受 1.x.x，但不要 2.0.0（minor / patch 自動升、major 不自動升）</li>\n<li><code>~1.2.0</code> ← 只接受 1.2.x</li>\n</ul>\n<p>生產環境如果不想被 minor 突然推大改動，<strong>建議用 <code>^x.y.z</code></strong> 鎖住 major。我們現在開發階段每個 service 都還在 0.x，所以圖方便都用 <code>&gt;=0.0.0</code>。</p>\n<h3 id=\"確認-imagepolicy-算出什麼\"><a class=\"anchor\" href=\"#確認-imagepolicy-算出什麼\">#</a> 確認 ImagePolicy 算出什麼？</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagepolicy</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">...</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">status:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  conditions:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> message:</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> |</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">        Latest</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> tag</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> for</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> harbor.thebarkingdog.tw/tbd/keyword_correction</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">        resolved</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> to</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">previously </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">harbor.thebarkingdog.tw/tbd/keyword_correction:0.13.1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      reason:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Succeeded</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">      status:</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">True</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      type</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Ready</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  latestRef:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    tag:</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span></span></code></pre>\n<p><code>latestRef.tag</code> 就是 Flux 認定的「最新」。下一步把它寫進 deployment.yaml 是 <code>ImageUpdateAutomation</code> 的工作。</p>\n<hr />\n<h2 id=\"五-imageupdateautomation把-policy-算出的版本寫進-git\"><a class=\"anchor\" href=\"#五-imageupdateautomation把-policy-算出的版本寫進-git\">#</a> 五、ImageUpdateAutomation：把 Policy 算出的版本寫進 Git</h2>\n<p>這是<strong>唯一會「動 Git」的 Flux 物件</strong>。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># clusters/stage/flux/automation.yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">apiVersion</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image.toolkit.fluxcd.io/v1</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ImageUpdateAutomation</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">metadata</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  namespace</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">spec</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  interval</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 1m0s</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  sourceRef</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    kind</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> GitRepository</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  git</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    checkout</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      ref</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">        branch</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> main</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    commit</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      author</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">        email</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> bot@gitea.com</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">        name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> gitea-bot</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      messageTemplate</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#1E754F;--shiki-dark:#4D9375\"> |</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        Automated image update</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        Automation name: &#x3C;!--swig￼0--></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        Files:</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        &#x3C;!--swig￼1--></span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        - &#x3C;!--swig￼2--></span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        &#x3C;!--swig￼3--></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        Objects:</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        &#x3C;!--swig￼4--></span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        - &#x3C;!--swig￼5--> &#x3C;!--swig￼6--></span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">        &#x3C;!--swig￼7--></span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    push</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">      branch</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> main</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  update</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    path</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ./apps/stage</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    strategy</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Setters</span></span></code></pre>\n<p>幾個關鍵點：</p>\n<h3 id=\"updatepath-只掃這個資料夾\"><a class=\"anchor\" href=\"#updatepath-只掃這個資料夾\">#</a> <code>update.path</code> —— 「只掃這個資料夾」</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">update</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  path</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ./apps/stage</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  strategy</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Setters</span></span></code></pre>\n<p>Flux 不會掃整個 repo，<strong>只在 <code>./apps/stage</code> 底下找可改的 yaml</strong>。</p>\n<p>如果你有 <code>apps/prod</code> 但<strong>目前 prod 不想自動升版</strong>，就不要設定到那邊（這也是我們的現況）。</p>\n<h3 id=\"strategy-setters-改寫機制是標記法\"><a class=\"anchor\" href=\"#strategy-setters-改寫機制是標記法\">#</a> <code>strategy: Setters</code> —— 改寫機制是「標記法」</h3>\n<p><code>Setters</code> 策略表示 Flux 不會自己猜哪個 yaml 該改，而是<strong>只改 yaml 上有特殊註解標記的位置</strong>：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># apps/stage/services/keyword-correction/deployment.yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">containers</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#999999;--shiki-dark:#666666\">  -</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> name</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> api</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">    image</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> harbor.thebarkingdog.tw/tbd/keyword_correction:0.13.3</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"> # &#123;\"$imagepolicy\": \"flux-system:keyword-correction\"&#125;</span></span></code></pre>\n<p>注意行尾的 <code># &#123;&quot;$imagepolicy&quot;: &quot;flux-system:keyword-correction&quot;&#125;</code> 註解——這就是 setter marker，意思是：</p>\n<blockquote>\n<p>「<strong>這一行的 image tag</strong>，要套用名為 <code>keyword-correction</code>（在 <code>flux-system</code> namespace）的 ImagePolicy 算出的最新版」。</p>\n</blockquote>\n<p>Flux 看到這行就知道：「啊，我要把這個 <code>:0.13.3</code> 改成 <code>keyword-correction</code> policy 的 latestRef.tag」。</p>\n<p><strong>沒這行註解 = Flux 不動這個 image</strong>。這是個刻意設計：<strong>讓 yaml 自己宣告「我要被自動更新」</strong>，避免 Flux 亂改其他 image（例如 sidecar、init container）。</p>\n<h3 id=\"gitpushbranch-改完-push-回哪\"><a class=\"anchor\" href=\"#gitpushbranch-改完-push-回哪\">#</a> <code>git.push.branch</code> —— 改完 push 回哪</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">push</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">  branch</span><span style=\"color:#999999;--shiki-dark:#666666\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> main</span></span></code></pre>\n<p>直接 push 進 main。所以<strong>這個 branch 不能 protect 到不准 bot push</strong>——不然 Flux 一直失敗。</p>\n<p>Gitea 上實際看到的 commit 長這樣：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>Author: gitea-bot &#x3C;bot@gitea.com></span></span>\n<span class=\"line\"><span>Date:   Wed May 13 15:06:14 2026 +0800</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>    Automated image update</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>    Automation name: flux-system/flux-system</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>    Files:</span></span>\n<span class=\"line\"><span>    - apps/stage/services/keyword-correction/deployment.yaml</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>    Objects:</span></span>\n<span class=\"line\"><span>    - Deployment keyword-correction</span></span></code></pre>\n<h3 id=\"interval-1m-automation-自己的輪詢\"><a class=\"anchor\" href=\"#interval-1m-automation-自己的輪詢\">#</a> <code>interval: 1m</code> —— Automation 自己的輪詢</h3>\n<p>注意這個 1 分鐘<strong>跟 ImagePolicy 的 5 分鐘是分開的</strong>。完整鏈：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>ImageRepository.interval  = 5m   ← 掃 Harbor</span></span>\n<span class=\"line\"><span>ImagePolicy               = 派生  ← 看 ImageRepository status 算</span></span>\n<span class=\"line\"><span>ImageUpdateAutomation.interval = 1m   ← 比對 ImagePolicy 跟 yaml 內容</span></span>\n<span class=\"line\"><span>GitRepository.interval    = 5m   ← 拉 Git（Image Automation 改完的 commit 也要靠它拉）</span></span>\n<span class=\"line\"><span>Kustomization.interval    = 10m  ← apply Git 進 cluster</span></span></code></pre>\n<p>理論最壞情況：<strong>5 + 1 + 5 + 10 ≈ 21 分鐘</strong>才能滾完。實務上 Harbor webhook 觸發後通常 1-3 分鐘可滾。</p>\n<hr />\n<h2 id=\"六-放在一起一次-bump-走完整鏈\"><a class=\"anchor\" href=\"#六-放在一起一次-bump-走完整鏈\">#</a> 六、放在一起：一次 bump 走完整鏈</h2>\n<p>把上面拆開的東西組起來，就是一個完整 bump 流程。我用我們前陣子真的踩過的 <code>0.13.3</code> bump 來重現（同時呈現踩到的坑）。</p>\n<h3 id=\"起點staging-branch-merge\"><a class=\"anchor\" href=\"#起點staging-branch-merge\">#</a> 起點：staging branch merge</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 工程師</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> checkout</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -b</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> bump/0.13.3</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 改 pyproject.toml 0.13.2 → 0.13.3</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> commit</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -am</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">🔖 bump: version 0.13.2 → 0.13.3</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> push</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> origin</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> bump/0.13.3</span></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 開 PR bump/0.13.3 → staging, force merge</span></span></code></pre>\n<p>merge 之後 staging branch HEAD 在 commit <code>4a84856</code>。</p>\n<h3 id=\"ci-build把-image-推到-harbor\"><a class=\"anchor\" href=\"#ci-build把-image-推到-harbor\">#</a> CI build：把 image 推到 Harbor</h3>\n<p><code>.gitea/workflows/push-to-harbor.yml</code> 觸發 build。內部 CI 的 tag 決定邏輯：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#B07D48;--shiki-dark:#BD976A\">IMAGE_TAG</span><span style=\"color:#999999;--shiki-dark:#666666\">=$</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> describe</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --tags</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --abbrev=0</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> 2</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\">></span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">/dev/null</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> ||</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> echo</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">latest</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span></code></pre>\n<p><code>git describe --tags --abbrev=0</code> 的意思是「<strong>從當前 commit 往回追溯，最近一個可達的 tag</strong>」。</p>\n<p><strong>🪤 第一個踩到的坑</strong>：我用 <code>cz bump</code> 在<strong>本地</strong>先打了 <code>0.13.3</code> tag、推上去——但這個 tag 指向<strong>本地 commit <code>919ed8c</code></strong>。後來 staging 被 force merge，產生了<strong>全新的 commit <code>4a84856</code></strong>（Gitea force merge 會重新打 commit hash）。所以從 <code>4a84856</code> 往回追溯時：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>4a84856 (staging)  ← 沒有 tag 指這裡</span></span>\n<span class=\"line\"><span>  ↓ parent</span></span>\n<span class=\"line\"><span>1aba89c            ← 沒有</span></span>\n<span class=\"line\"><span>  ↓ parent</span></span>\n<span class=\"line\"><span>026d938            ← tag: 0.13.2 ✅ 找到了</span></span></code></pre>\n<p>CI 算出 <code>IMAGE_TAG = 0.13.2</code>，於是 build 出來的 image 是 <code>keyword_correction:0.13.2</code>，<strong>把 Harbor 上原本的 0.13.2 image 覆蓋掉了</strong>。</p>\n<p>修法：把 tag 重打到正確的 commit。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> tag</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -d</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">                    # 刪本地舊 tag</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> push</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> origin</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> :refs/tags/0.13.3</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">    # 刪遠端舊 tag</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> tag</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> origin/staging</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">        # 重打到 4a84856</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> push</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> origin</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span></span></code></pre>\n<p>然後 rerun CI workflow，這次才 build 出 <code>0.13.3</code> image。</p>\n<p>教訓：<strong>如果 CI 要靠 tag 決定 image version，tag 跟 branch HEAD 一定要對齊</strong>。<code>cz bump</code> 假設 dev 可以直接 push，但 protected branch + PR 流會把這個假設打破。</p>\n<h3 id=\"imagerepository-看到新-tag\"><a class=\"anchor\" href=\"#imagerepository-看到新-tag\">#</a> ImageRepository 看到新 tag</h3>\n<p>5 分鐘內 image-reflector-controller 輪詢 Harbor、看到 <code>0.13.3</code>：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> describe</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagerepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">...</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">Status:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  Last</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Scan</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Result:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    Tag</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Count:</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 8</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">     # ← 從 7 變成 8</span></span></code></pre>\n<h3 id=\"imagepolicy-重新算出-latestref\"><a class=\"anchor\" href=\"#imagepolicy-重新算出-latestref\">#</a> ImagePolicy 重新算出 latestRef</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagepolicy</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> yaml</span></span>\n<span class=\"line\"><span style=\"color:#998418;--shiki-dark:#B8A965\">...</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">status:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  conditions:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    -</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> message:</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> |</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">        Latest</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> tag</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ...</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> resolved</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> to</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">previously </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">...:0.13.1</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">  latestRef:</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">    tag:</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\"> 0.13.3</span><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\">   # ← 升上來了</span></span></code></pre>\n<h3 id=\"imageupdateautomation-改-yaml-commit\"><a class=\"anchor\" href=\"#imageupdateautomation-改-yaml-commit\">#</a> ImageUpdateAutomation 改 yaml + commit</h3>\n<p>1 分鐘內，automation 比對 ImagePolicy.latestRef vs deployment.yaml：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-diff\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># apps/stage/services/keyword-correction/deployment.yaml</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">   containers:</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">     - name: api</span></span>\n<span class=\"line\"><span style=\"color:#B31D28;--shiki-dark:#FDAEB7\">-      image: harbor.thebarkingdog.tw/tbd/keyword_correction:0.13.2 # </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#B31D28;--shiki-dark:#FDAEB7\">\"$imagepolicy\": \"flux-system:keyword-correction\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">+      image: harbor.thebarkingdog.tw/tbd/keyword_correction:0.13.3 # </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#22863A;--shiki-dark:#85E89D\">\"$imagepolicy\": \"flux-system:keyword-correction\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">     - name: streamlit</span></span>\n<span class=\"line\"><span style=\"color:#B31D28;--shiki-dark:#FDAEB7\">-      image: harbor.thebarkingdog.tw/tbd/keyword_correction:0.13.2 # </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#B31D28;--shiki-dark:#FDAEB7\">\"$imagepolicy\": \"flux-system:keyword-correction\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">+      image: harbor.thebarkingdog.tw/tbd/keyword_correction:0.13.3 # </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#123;</span><span style=\"color:#22863A;--shiki-dark:#85E89D\">\"$imagepolicy\": \"flux-system:keyword-correction\"</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">&#125;</span></span></code></pre>\n<p>由 <code>gitea-bot</code> commit + push 進 main：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> git</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> log</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> apps/stage/services/keyword-correction/deployment.yaml</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --oneline</span><span style=\"color:#AB5959;--shiki-dark:#CB7676\"> |</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> head</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -3</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">f15b4e7</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Automated</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> update</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">e6df15b</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> ♻️</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> refactor</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#59873A;--shiki-dark:#80A665\">keyword-correction</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">:</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> 改用合一</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\"> </span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">(</span><span style=\"color:#393A34;--shiki-dark:#DBD7CAEE\">api </span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">+</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> streamlit</span><span style=\"color:#2993a3;--shiki-dark:#5eaab5\">)</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">dd31385</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> Automated</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> image</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> update</span></span></code></pre>\n<h3 id=\"gitrepository-拉新-commit\"><a class=\"anchor\" href=\"#gitrepository-拉新-commit\">#</a> GitRepository 拉新 commit</h3>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> gitrepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> jsonpath=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;.status.artifact.revision&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">main@sha1:f15b4e7cb16d276a1d3aae250c2c5e9f85a6050f</span></span></code></pre>\n<p><strong>🪤 第二個踩到的坑</strong>：Image Automation 把 commit push 進 Git 後<strong>自己不會立刻拉</strong>。GitRepository 預設 5 分鐘輪詢——所以 commit push 後可能要等 5 分鐘 cluster 才同步。</p>\n<p>要快可以手動戳：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> annotate</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> gitrepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  reconcile.fluxcd.io/requestedAt=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">$(</span><span style=\"color:#59873A;--shiki-dark:#80A665\">date</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> +%s</span><span style=\"color:#999999;--shiki-dark:#666666\">)</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --overwrite</span></span></code></pre>\n<p>這個 annotation 不是「設定一個值」，是「<strong>觸發一次 reconcile</strong>」——Flux controller 看到這個 annotation 改變就知道「該重新跑了」。</p>\n<h3 id=\"kustomization-apply\"><a class=\"anchor\" href=\"#kustomization-apply\">#</a> Kustomization apply</h3>\n<p>GitRepository 拉到新 commit 後，kustomize-controller 比對 git vs cluster、發現 deployment.yaml 變了、執行 apply。</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> pod</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -l</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> app=keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">NAME</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                                  READY</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   STATUS</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">              RESTARTS</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   AGE</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">keyword-correction-54c65f7cbc-fddts</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   0/2</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">     ContainerCreating</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">   0</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">          5s</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">keyword-correction-d59d875c5-mp8ms</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">    2/2</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">     Running</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">             1</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">          4d11h</span></span></code></pre>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 30 秒後</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">$</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kubectl</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> pod</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -l</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> app=keyword-correction</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">NAME</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">                                  READY</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   STATUS</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">    RESTARTS</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   AGE</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">keyword-correction-54c65f7cbc-fddts</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">   2/2</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">     Running</span><span style=\"color:#2F798A;--shiki-dark:#4C9A91\">   0</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">          31s</span></span></code></pre>\n<p>舊 pod 被殺、新 pod 跑 <code>0.13.3</code>、bug 修了 ✅</p>\n<hr />\n<h2 id=\"七-所有可觀察狀態的指令清單\"><a class=\"anchor\" href=\"#七-所有可觀察狀態的指令清單\">#</a> 七、所有可觀察狀態的指令清單</h2>\n<p>放這裡當未來自己 cheat sheet：</p>\n<pre class=\"shiki shiki-themes vitesse-light vitesse-dark\" style=\"background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 1. Harbor 有哪些 tag (Flux 看到的)</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> describe</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagerepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#AB5959;--shiki-dark:#CB7676\">  |</span><span style=\"color:#59873A;--shiki-dark:#80A665\"> grep</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\"> \"</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">Tag Count\\|Latest Scan</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 2. ImagePolicy 算出哪個是最新版</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagepolicy</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> jsonpath=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;.status.latestRef.tag&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> echo</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 3. ImageUpdateAutomation 上次成功 push 哪個 commit</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imageupdateautomation</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> jsonpath=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;.status.lastPushCommit&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> echo</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imageupdateautomation</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> jsonpath=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;.status.lastPushTime&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> echo</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 4. GitRepository 目前拉到哪個 commit</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> gitrepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> jsonpath=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;.status.artifact.revision&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> echo</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 5. Kustomization 套用狀態</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> get</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> kustomization</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> apps</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#A65E2B;--shiki-dark:#C99076\">  -o</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> jsonpath=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\">&#123;.status.lastAppliedRevision&#125;</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">'</span><span style=\"color:#999999;--shiki-dark:#666666\">;</span><span style=\"color:#998418;--shiki-dark:#B8A965\"> echo</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 6. 強制 GitRepository 立刻 reconcile</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> annotate</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> gitrepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  reconcile.fluxcd.io/requestedAt=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">$(</span><span style=\"color:#59873A;--shiki-dark:#80A665\">date</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> +%s</span><span style=\"color:#999999;--shiki-dark:#666666\">)</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --overwrite</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 7. 強制 ImageRepository 立刻 scan</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> annotate</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> imagerepository</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> keyword-correction</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#B56959;--shiki-dark:#C98A7D\">  reconcile.fluxcd.io/requestedAt=</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#999999;--shiki-dark:#666666\">$(</span><span style=\"color:#59873A;--shiki-dark:#80A665\">date</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> +%s</span><span style=\"color:#999999;--shiki-dark:#666666\">)</span><span style=\"color:#B5695977;--shiki-dark:#C98A7D77\">\"</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --overwrite</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#A0ADA0;--shiki-dark:#758575DD\"># 8. 看 controller logs（debug 用）</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> deploy/image-automation-controller</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --tail=50</span></span>\n<span class=\"line\"><span style=\"color:#59873A;--shiki-dark:#80A665\">kubectl</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> -n</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> flux-system</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> logs</span><span style=\"color:#B56959;--shiki-dark:#C98A7D\"> deploy/source-controller</span><span style=\"color:#A65E2B;--shiki-dark:#C99076\"> --tail=50</span></span></code></pre>\n<hr />\n<h2 id=\"八-設計上的取捨\"><a class=\"anchor\" href=\"#八-設計上的取捨\">#</a> 八、設計上的取捨</h2>\n<h3 id=\"為什麼要走-git不直接改-deployment\"><a class=\"anchor\" href=\"#為什麼要走-git不直接改-deployment\">#</a> 為什麼要走 Git，不直接改 Deployment？</h3>\n<p>第一次看 Image Automation 的人常問：「<strong>Flux 都裝在 cluster 裡了，為什麼不直接 <code>kubectl set image</code> 改 Deployment？</strong>」</p>\n<p>答案是 <strong>Git 的 audit 功能</strong>：</p>\n<ul>\n<li>直接改 cluster → 過幾個月誰也不知道 pod 跑的 image 是哪天部署的</li>\n<li>透過 Git → <code>git log apps/stage/.../deployment.yaml</code> 直接看時間軸</li>\n</ul>\n<p>而且 disaster recovery：cluster 整個壞掉重建，從 Git 一拉就回到上次最新狀態——前提是 Git 真的有最新狀態。</p>\n<h3 id=\"為什麼-imagepolicy-跟-update-要分開\"><a class=\"anchor\" href=\"#為什麼-imagepolicy-跟-update-要分開\">#</a> 為什麼 ImagePolicy 跟 Update 要分開？</h3>\n<p>兩者責任不同：</p>\n<ul>\n<li><strong>ImagePolicy</strong> 是「<strong>算出最新版</strong>」（純函式：input 是 tag 列表，output 是一個 tag）</li>\n<li><strong>ImageUpdateAutomation</strong> 是「<strong>改 Git</strong>」（有 side effect：寫檔、commit、push）</li>\n</ul>\n<p>分開的好處：<strong>同一個 ImagePolicy 可以被多個 deployment.yaml 同時引用</strong>。<code>flux-system:keyword-correction</code> 這個 policy 同時被 api container 跟 streamlit container 引用（marker 寫一樣的），兩個位置都會被改。</p>\n<h3 id=\"為什麼預設要走輪詢\"><a class=\"anchor\" href=\"#為什麼預設要走輪詢\">#</a> 為什麼預設要走輪詢？</h3>\n<p>理論上 Harbor → Flux webhook 可以做到 0 延遲，但實務上：</p>\n<ul>\n<li>防火牆/Ingress 設定容易卡 webhook</li>\n<li>webhook 漏了就完全沒救</li>\n<li>輪詢是<strong>最終一致性</strong>的保證</li>\n</ul>\n<p>所以 Flux 採取<strong>輪詢為 baseline、webhook 為加速器</strong>的設計。</p>\n<hr />\n<h2 id=\"九-給未來自己的提醒\"><a class=\"anchor\" href=\"#九-給未來自己的提醒\">#</a> 九、給未來自己的提醒</h2>\n<p>整理一些常踩的坑：</p>\n<table>\n<thead>\n<tr>\n<th>症狀</th>\n<th>大概率原因</th>\n<th>怎麼查</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Harbor 有新 image，cluster 沒滾</td>\n<td>ImagePolicy 還沒算出新 latestRef</td>\n<td><code>kubectl get imagepolicy -o yaml</code> 看 <code>latestRef.tag</code></td>\n</tr>\n<tr>\n<td>ImagePolicy 算出來不是預期的版本</td>\n<td><code>filterTags.pattern</code> regex 錯了</td>\n<td>手動 <code>echo &quot;tag&quot; \\| grep -E &quot;pattern&quot;</code> 試</td>\n</tr>\n<tr>\n<td>ImagePolicy OK 但 yaml 沒被改</td>\n<td>忘記在 yaml 加 <code># &#123;&quot;$imagepolicy&quot;: ...&#125;</code> marker</td>\n<td>grep 那個 yaml 檔有沒有 <code>$imagepolicy</code> 字串</td>\n</tr>\n<tr>\n<td>yaml 被改了但 cluster 沒套</td>\n<td>GitRepository 還沒 reconcile</td>\n<td>戳 annotate 強制 reconcile</td>\n</tr>\n<tr>\n<td><code>Automated image update</code> commit 一直失敗</td>\n<td>bot 對 main branch 沒寫權限 / branch protect 擋</td>\n<td>看 image-automation-controller log</td>\n</tr>\n<tr>\n<td>CI build 出來 image tag 是錯的</td>\n<td><code>git describe --tags</code> 找不到正確 tag，或 tag 指錯 commit</td>\n<td>確認 staging HEAD 上有 tag</td>\n</tr>\n</tbody>\n</table>\n<hr />\n<h2 id=\"結語\"><a class=\"anchor\" href=\"#結語\">#</a> 結語</h2>\n<p>Image Automation 是 Flux 最有「魔法感」的功能，但拆開看就是四件清楚的事：</p>\n<ol>\n<li><strong>掃 Harbor</strong> (ImageRepository)</li>\n<li><strong>算最新</strong> (ImagePolicy)</li>\n<li><strong>改 Git</strong> (ImageUpdateAutomation + setter marker)</li>\n<li><strong>同步進 cluster</strong> (GitRepository + Kustomization，跟前一篇一樣)</li>\n</ol>\n<p>關鍵是「<strong>Flux 不繞過 Git</strong>」——這保留了所有變更的 audit trail，也讓 disaster recovery 變得簡單。</p>\n<p>下次 push image 看 5 分鐘內 cluster 自己升版，再也不用手動改 yaml 了 🚀</p>\n<hr />\n<h2 id=\"系列文章\"><a class=\"anchor\" href=\"#系列文章\">#</a> 系列文章</h2>\n<blockquote>\n<p><strong>GitOps 實戰系列</strong></p>\n<ul>\n<li><strong>(一)</strong> <a href=\"https://akebee.com/gitops-kustomize-fluxcd-gpu-k8s/\">GitOps 實戰：用 Kustomize + FluxCD 管 GPU K8s 叢集</a>（架構總覽、Kustomize 分層、SOPS/age bootstrap）</li>\n<li><strong>(二)</strong> <a href=\"https://akebee.com/gitops-image-automation/\">GitOps 實戰：FluxCD Image Automation 實戰</a>（4 CRD 詳解、filterTags、bump 踩坑全記錄）</li>\n</ul>\n</blockquote>\n",
            "tags": [
                "GitOps",
                "DevOps",
                "Kubernetes",
                "FluxCD"
            ]
        }
    ]
}