<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Go-zero Archives - 原立方</title>
	<atom:link href="https://www.atomic-cube.cn/tag/go-zero/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.atomic-cube.cn/tag/go-zero/</link>
	<description>技术栈的流动-从零到∞</description>
	<lastBuildDate>Sat, 23 May 2026 07:53:49 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>
	<item>
		<title>Go深度指南：从基础到高级的全面解析</title>
		<link>https://www.atomic-cube.cn/go%e6%b7%b1%e5%ba%a6%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e5%9f%ba%e7%a1%80%e5%88%b0%e9%ab%98%e7%ba%a7%e7%9a%84%e5%85%a8%e9%9d%a2%e8%a7%a3%e6%9e%90/</link>
					<comments>https://www.atomic-cube.cn/go%e6%b7%b1%e5%ba%a6%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e5%9f%ba%e7%a1%80%e5%88%b0%e9%ab%98%e7%ba%a7%e7%9a%84%e5%85%a8%e9%9d%a2%e8%a7%a3%e6%9e%90/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Sat, 23 May 2026 07:25:58 +0000</pubDate>
				<category><![CDATA[go-zero]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=2098</guid>

					<description><![CDATA[<p>Go 语言自推出以来，因其简洁性、并发支持和高效性能，在云计算、微服务和分布式系统中广泛应用。作为开发者，面试 [&#8230;]</p>
<p>The post <a href="https://www.atomic-cube.cn/go%e6%b7%b1%e5%ba%a6%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e5%9f%ba%e7%a1%80%e5%88%b0%e9%ab%98%e7%ba%a7%e7%9a%84%e5%85%a8%e9%9d%a2%e8%a7%a3%e6%9e%90/">Go深度指南：从基础到高级的全面解析</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Go 语言自推出以来，因其简洁性、并发支持和高效性能，在云计算、微服务和分布式系统中广泛应用。作为开发者，面试时往往需要面对一系列技术问题，这些常被称为“八股文”的题目，虽然有时显得高级特性，力求每个解释都严谨实用，避免空话套话。让我们从最基础的部分开始。<br></p>



<h1 class="wp-block-heading">第一章：基础概念与语法</h1>



<h2 class="wp-block-heading">变量与类型系统<a href="http://is.iceymoss.com/blog/36#%E5%8F%98%E9%87%8F%E4%B8%8E%E7%B1%BB%E5%9E%8B%E7%B3%BB%E7%BB%9F"></a></h2>



<p class="wp-block-paragraph">Go 是静态类型语言，类型安全且编译时检查。变量声明使用&nbsp;<code>var</code>&nbsp;关键字，或短变量声明&nbsp;<code>:=</code>。基础类型包括整型、浮点型、布尔型和字符串等。理解零值概念至关重要：未初始化的变量会赋予其类型的零值，例如整型为 0，字符串为空字符串&#8221;&#8221;。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

func main() {
    var a int      // 声明整型变量，零值为0
    b := 10        // 短变量声明，类型推断为int
    var s string   // 字符串零值为""
    fmt.Printf("a: %d, b: %d, s: %s\n", a, b, s) // 输出: a: 0, b: 10, s: 
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">a</span><span style="color: #D8DEE9FF"> int      </span><span style="color: #616E88">// 声明整型变量，零值为0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    b </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">// 短变量声明，类型推断为int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">s</span><span style="color: #D8DEE9FF"> string   </span><span style="color: #616E88">// 字符串零值为&quot;&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">a: %d, b: %d, s: %s</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">s</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: a: 0, b: 10, s: </span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">类型转换必须是显式的，Go 不支持隐式类型转换。例如，将 <code>int</code> 转换为 <code>float64</code> 需要使用 <code>float64(x)</code>。这有助于避免意外错误。</p>



<h2 class="wp-block-heading">控制结构</h2>



<p class="wp-block-paragraph">Go 的控制结构包括 <code>if</code>、<code>for</code>、<code>switch</code> 等，设计简洁。<code>for</code> 循环是唯一的循环结构，但可以模拟 <code>while</code> 循环。<code>if</code> 语句可以包含初始化语句，增强可读性。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

func main() {
    // for循环示例
    for i := 0; i &lt; 5; i++ {
        fmt.Println(i)
    }
    // 类似while循环
    j := 0
    for j &lt; 3 {
        fmt.Println(j)
        j++
    }
    // if带初始化
    if x := 10; x > 5 {
        fmt.Println("x大于5")
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// for循环示例</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 类似while循环</span></span>
<span class="line"><span style="color: #D8DEE9FF">    j </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">j</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">j</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">j</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// if带初始化</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> x </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">x</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">x大于5</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>switch</code> 语句在 Go 中更为灵活，case 表达式可以是常量、变量或函数调用，且默认不需要 <code>break</code>。</p>



<h2 class="wp-block-heading">函数</h2>



<p class="wp-block-paragraph">函数是 Go 的一等公民，支持多返回值，这在错误处理中尤为常见。函数可以定义为方法，与类型关联。理解值传递和引用传递的区别很重要：Go 中所有参数都是值传递，但对于切片、映射和通道等引用类型，传递的是底层数据的引用。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

// 多返回值函数
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为零")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result) // 输出: 结果: 5
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 多返回值函数</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">divide</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">除数不能为零</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">a</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">divide</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">错误:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">结果:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: 结果: 5</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">第二章：并发编程核心</h1>



<p class="wp-block-paragraph">Go 的并发模型基于 goroutine 和 channel，是其最强大的特性之一。面试中常深入探讨这方面。</p>



<h2 class="wp-block-heading">goroutine<a href="http://is.iceymoss.com/blog/36#goroutine"></a></h2>



<p class="wp-block-paragraph">goroutine 是轻量级线程，由 Go 运行时管理。使用&nbsp;<code>go</code>&nbsp;关键字启动，开销小，可轻松创建成千上万个。但需要注意，goroutine 的执行顺序不确定，依赖于调度器。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i &lt; 3; i++ {
        fmt.Println("Hello,", name)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go sayHello("Alice")  // 启动goroutine
    go sayHello("Bob")
    time.Sleep(1 * time.Second) // 等待goroutine完成，实际应用中应使用同步机制
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sayHello</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">name</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Hello,</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">name</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">100</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sayHello</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Alice</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)  </span><span style="color: #616E88">// 启动goroutine</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sayHello</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Bob</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 等待goroutine完成，实际应用中应使用同步机制</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">channel</h2>



<p class="wp-block-paragraph">channel 是 goroutine 间的通信管道，可以传递数据并同步执行。分为有缓冲和无缓冲两种。无缓冲 channel 要求发送和接收同时就绪，否则会阻塞；有缓冲 channel 在缓冲区满或空时阻塞。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

func main() {
    // 无缓冲channel
    ch := make(chan int)
    go func() {
        ch &lt;- 42 // 发送数据
    }()
    value := &lt;-ch // 接收数据
    fmt.Println("接收值:", value) // 输出: 接收值: 42

    // 有缓冲channel
    bufferedCh := make(chan string, 2)
    bufferedCh &lt;- "hello"
    bufferedCh &lt;- "world"
    fmt.Println(&lt;-bufferedCh, &lt;-bufferedCh) // 输出: hello world
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 无缓冲channel</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ch </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">ch</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 发送数据</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    value </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ch</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 接收数据</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">接收值:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: 接收值: 42</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 有缓冲channel</span></span>
<span class="line"><span style="color: #D8DEE9FF">    bufferedCh </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">bufferedCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">hello</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">bufferedCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">world</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">bufferedCh</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">bufferedCh</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: hello world</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">同步原语</h1>



<p class="wp-block-paragraph">除了 channel，Go 的 <code>sync</code> 包提供了 Mutex、WaitGroup 等同步工具。在共享资源访问时，使用 Mutex 避免数据竞争。WaitGroup 用于等待一组 goroutine 完成。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "sync"
    "time"
)

var counter int
var mu sync.Mutex

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    counter++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i &lt; 1000; i++ {
        wg.Add(1)
        go increment(&amp;wg)
    }
    wg.Wait()
    fmt.Println("最终计数器值:", counter) // 应该输出1000
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">counter</span><span style="color: #D8DEE9FF"> int</span></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mu</span><span style="color: #D8DEE9FF"> sync.Mutex</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">increment</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">WaitGroup</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">counter</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> sync.WaitGroup</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1000</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">increment</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">最终计数器值:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">counter</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 应该输出1000</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">第三章：高级特性与设计模式</h1>



<h2 class="wp-block-heading">接口与多态<a href="http://is.iceymoss.com/blog/36#%E6%8E%A5%E5%8F%A3%E4%B8%8E%E5%A4%9A%E6%80%81"></a></h2>



<p class="wp-block-paragraph">Go 的接口是隐式实现的：类型只需实现接口所有方法，就自动满足该接口。这促进了松耦合设计。接口常用于定义行为，如&nbsp;<code>io.Reader</code>&nbsp;和&nbsp;<code>io.Writer</code>。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

// 定义接口
type Speaker interface {
    Speak() string
}

// 结构体实现接口
type Dog struct{}

func (d Dog) Speak() string {
    return "Woof!"
}

type Cat struct{}

func (c Cat) Speak() string {
    return "Meow!"
}

func makeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    dog := Dog{}
    cat := Cat{}
    makeSound(dog) // 输出: Woof!
    makeSound(cat) // 输出: Meow!
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 定义接口</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Speaker interface {</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Speak() string</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 结构体实现接口</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Dog struct{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Dog</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Speak</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Woof!</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Cat struct{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Cat</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Speak</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Meow!</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">makeSound</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">s</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Speaker</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">s</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Speak</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    dog </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Dog</span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    cat </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Cat</span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">makeSound</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dog</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: Woof!</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">makeSound</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">cat</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: Meow!</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">空接口 <code>interface{}</code> 可以表示任何类型，但使用时应谨慎，通常与类型断言结合。</p>



<h2 class="wp-block-heading">错误处理</h2>



<p class="wp-block-paragraph">Go 的错误处理通过返回值实现，而非异常。标准库提供了 <code>error</code> 接口。建议使用 <code>errors.New</code> 或 <code>fmt.Errorf</code> 创建错误，并通过 <code>if err != nil</code> 检查。defer、panic 和 recover 用于处理异常情况，但 panic 应仅用于不可恢复错误。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "errors"
    "fmt"
)

func process(value int) (int, error) {
    if value &lt; 0 {
        return 0, errors.New("值不能为负")
    }
    return value * 2, nil
}

func safeProcess() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("恢复panic:", r)
        }
    }()
    panic("测试panic")
}

func main() {
    result, err := process(5)
    if err != nil {
        fmt.Println("错误:", err)
    } else {
        fmt.Println("结果:", result) // 输出: 结果: 10
    }
    safeProcess() // 输出: 恢复panic: 测试panic
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">errors</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">process</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">值不能为负</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">safeProcess</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> r </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">recover</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">r</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">恢复panic:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">r</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">panic</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">测试panic</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">process</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">错误:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">结果:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: 结果: 10</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">safeProcess</span><span style="color: #D8DEE9FF">() </span><span style="color: #616E88">// 输出: 恢复panic: 测试panic</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">反射与元编程</h2>



<p class="wp-block-paragraph">反射通过 <code>reflect</code> 包实现，允许程序在运行时检查类型和值。尽管强大，但反射性能开销大，应仅在必要时使用，如序列化或通用函数中。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "reflect"
)

func inspectType(v interface{}) {
    t := reflect.TypeOf(v)
    fmt.Printf("类型: %v, 种类: %v\n", t, t.Kind())
}

func main() {
    var x int = 42
    inspectType(x) // 输出: 类型: int, 种类: int
    inspectType("hello") // 输出: 类型: string, 种类: string
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">reflect</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">inspectType</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">interface</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    t </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">reflect</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TypeOf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">类型: %v, 种类: %v</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Kind</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">x</span><span style="color: #D8DEE9FF"> int </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">42</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">inspectType</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">x</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: 类型: int, 种类: int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">inspectType</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">hello</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: 类型: string, 种类: string</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">第四章：内存管理与性能优化</h1>



<h2 class="wp-block-heading">指针与值语义<a href="http://is.iceymoss.com/blog/36#%E6%8C%87%E9%92%88%E4%B8%8E%E5%80%BC%E8%AF%AD%E4%B9%89"></a></h2>



<p class="wp-block-paragraph">Go 有指针，但不像 C 那样复杂。指针允许直接操作内存地址，常用于避免大结构体复制的开销。值语义和引用语义的选择依赖于场景：值语义更安全，引用语义更高效。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// 值接收者
func (p Person) String() string {
    return fmt.Sprintf("%s (%d岁)", p.Name, p.Age)
}

// 指针接收者，可修改结构体
func (p *Person) Birthday() {
    p.Age++
}

func main() {
    p1 := Person{"Alice", 30}
    p1.Birthday() // 即使p1是值，Go会自动取地址
    fmt.Println(p1.String()) // 输出: Alice (31岁)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Person struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Name string</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Age  int</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 值接收者</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">p</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Person</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">String</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sprintf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">%s (%d岁)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">p</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Name</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">p</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Age</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 指针接收者，可修改结构体</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">p</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Person</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Birthday</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">p</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Age</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    p1 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Person</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Alice</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">30</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">p1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Birthday</span><span style="color: #D8DEE9FF">() </span><span style="color: #616E88">// 即使p1是值，Go会自动取地址</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">p1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">String</span><span style="color: #D8DEE9FF">()) </span><span style="color: #616E88">// 输出: Alice (31岁)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">垃圾回收<a href="http://is.iceymoss.com/blog/36#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"></a></h2>



<p class="wp-block-paragraph">Go 使用并发标记清除垃圾回收器，自动管理内存。开发者无需手动释放内存，但应注意避免内存泄漏，如未关闭的资源或全局变量引用。通过&nbsp;<code>runtime</code>&nbsp;包可以监控 GC 行为。</p>



<h2 class="wp-block-heading">性能调优<a href="http://is.iceymoss.com/blog/36#%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98"></a></h2>



<p class="wp-block-paragraph">性能优化包括减少分配、使用 sync.Pool 重用对象、避免不必要的反射等。工具如&nbsp;<code>pprof</code>&nbsp;用于分析 CPU 和内存使用。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "sync"
)

var pool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 1024)
    },
}

func main() {
    buf := pool.Get().([]byte)
    // 使用buf...
    fmt.Println("缓冲区长度:", len(buf))
    pool.Put(buf) // 放回池中以重用
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Pool</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">New</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">interface</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        return </span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">([]</span><span style="color: #D8DEE9">byte</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> 1024</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    buf </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pool</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">([]</span><span style="color: #D8DEE9">byte</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 使用buf...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">缓冲区长度:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">len</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">buf</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">pool</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Put</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">buf</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 放回池中以重用</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">第五章：标准库与实战问题</h1>



<h3 class="wp-block-heading">常用标准库<a href="http://is.iceymoss.com/blog/36#%E5%B8%B8%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93"></a></h3>



<p class="wp-block-paragraph">Go 标准库丰富，面试常问&nbsp;<code>net/http</code>、<code>encoding/json</code>、<code>testing</code>&nbsp;等。例如，HTTP 服务器实现简单：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path&#91;1:&#93;)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">net/http</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handler</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">w</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ResponseWriter</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">r</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Request</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fprintf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">w</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Hello, %s!</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">r</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">URL</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Path</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">:&#93;)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">HandleFunc</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">handler</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ListenAndServe</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">:8080</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">JSON 序列化和反序列化：</h2>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    user := User{"Bob", 25}
    data, err := json.Marshal(user)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println(string(data)) // 输出: {"name":"Bob","age":25}
    var newUser User
    json.Unmarshal(data, &amp;newUser)
    fmt.Println(newUser) // 输出: {Bob 25}
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">encoding/json</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> User struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Name string `json:&quot;name&quot;`</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Age  int    `json:&quot;age&quot;`</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    user </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">User</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Bob</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">25</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">json</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Marshal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">错误:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)) </span><span style="color: #616E88">// 输出: {&quot;name&quot;:&quot;Bob&quot;,&quot;age&quot;:25}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">newUser</span><span style="color: #D8DEE9FF"> User</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">json</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unmarshal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">newUser</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">newUser</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 输出: {Bob 25}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">常见面试题解析<a href="http://is.iceymoss.com/blog/36#%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E8%A7%A3%E6%9E%90"></a></h2>



<p class="wp-block-paragraph">面试中常出现的问题包括：goroutine 泄漏如何避免、channel 死锁场景、接口设计原则等。例如，goroutine 泄漏通常因未正确关闭 channel 或忽略错误导致，解决方案是使用&nbsp;<code>context</code>&nbsp;包进行取消。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, ch chan int) {
    for {
        select {
        case &lt;-ctx.Done():
            fmt.Println("worker停止")
            return
        case val := &lt;-ch:
            fmt.Println("处理值:", val)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    ch := make(chan int)
    go worker(ctx, ch)
    ch &lt;- 1
    time.Sleep(1 * time.Second)
    cancel() // 取消worker，避免泄漏
    time.Sleep(100 * time.Millisecond)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">worker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ch</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">():</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">worker停止</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> val </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF">ch</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">处理值:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithCancel</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ch </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">worker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ch</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ch</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">() </span><span style="color: #616E88">// 取消worker，避免泄漏</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">100</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">第六章：最佳实践与总结</h1>



<p class="wp-block-paragraph">在 Go 开发中，遵循最佳实践能提升代码质量：使用 go fmt 统一格式、编写单元测试、依赖管理使用 go mod、避免全局状态等。测试是 Go 文化的一部分，内置 testing 包支持表格驱动测试。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="复制" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "testing"
)

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d) = %d; 期望 %d", tt.a, tt.b, result, tt.expected)
        }
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">testing</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">a</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestAdd</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    tests </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">struct</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">expected</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">{</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">{</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">{</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> tt </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tests</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        result </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tt</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tt</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">b</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tt</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">expected</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Add(%d, %d) = %d; 期望 %d</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tt</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tt</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">b</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tt</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">expected</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">总结来说，Go 需要对语言特性的深刻理解。通过本文的梳理，我们希望读者能掌握从基础语法到并发模型、从接口设计到性能优化的核心点。实际编码中，多实践、多阅读优秀源码是提升的关键。持续学习才能应对不断变化的技术挑战。</p>
<p>The post <a href="https://www.atomic-cube.cn/go%e6%b7%b1%e5%ba%a6%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e5%9f%ba%e7%a1%80%e5%88%b0%e9%ab%98%e7%ba%a7%e7%9a%84%e5%85%a8%e9%9d%a2%e8%a7%a3%e6%9e%90/">Go深度指南：从基础到高级的全面解析</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/go%e6%b7%b1%e5%ba%a6%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e5%9f%ba%e7%a1%80%e5%88%b0%e9%ab%98%e7%ba%a7%e7%9a%84%e5%85%a8%e9%9d%a2%e8%a7%a3%e6%9e%90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Go 语言文件处理</title>
		<link>https://www.atomic-cube.cn/go-%e8%af%ad%e8%a8%80%e6%96%87%e4%bb%b6%e5%a4%84%e7%90%86/</link>
					<comments>https://www.atomic-cube.cn/go-%e8%af%ad%e8%a8%80%e6%96%87%e4%bb%b6%e5%a4%84%e7%90%86/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Tue, 28 Apr 2026 23:37:11 +0000</pubDate>
				<category><![CDATA[Gin]]></category>
		<category><![CDATA[go-zero]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1952</guid>

					<description><![CDATA[<p>文件处理是 Go 语言中最常见的操作之一——读取配置、写入日志、数据持久化都离不开它。Go 的标准库提供了一套简洁而强大的文件 I/O 接口，覆盖从单次读写到流式处理的各种场景</p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e8%af%ad%e8%a8%80%e6%96%87%e4%bb%b6%e5%a4%84%e7%90%86/">Go 语言文件处理</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">文件处理是 Go 语言中最常见的操作之一——读取配置、写入日志、数据持久化都离不开它。Go 的标准库提供了一套简洁而强大的文件 I/O 接口，覆盖从单次读写到流式处理的各种场景。</p>



<p class="wp-block-paragraph">与文件处理相关的核心包有 5 个，各有分工：</p>



<figure class="wp-block-image"><a href="https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-scaled.png"><img fetchpriority="high" decoding="async" width="2560" height="986" src="https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-scaled.png" alt="" class="wp-image-1954" srcset="https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-scaled.png 2560w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-300x116.png 300w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-1024x394.png 1024w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-768x296.png 768w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-1536x592.png 1536w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/os-2048x789.png 2048w" sizes="(max-width: 2560px) 100vw, 2560px" /></a></figure>



<p class="wp-block-paragraph"></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>版本提示：</strong><code>ioutil</code> 包在 Go 1.16 已弃用，其功能已迁移到 <code>os</code>（如 <code>os.ReadFile</code>、<code>os.WriteFile</code>）和 <code>io</code>（如 <code>io.ReadAll</code>）。新代码应直接使用 <code>os</code> 和 <code>io</code> 包。</p>
</blockquote>



<h2 class="wp-block-heading">1. 文件创建</h2>



<p class="wp-block-paragraph"><code>os.Create</code> 创建一个新文件。如果文件已存在，<span class="marked">会被截断（清空内容）</span>。返回的文件对象必须关闭以释放系统资源：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "log"
        "os"
)

func main() {
        // os.Create 创建文件（已存在则清空）
        file, err := os.Create("test.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close() // defer 确保函数结束时关闭文件

        // 写入内容验证创建成功
        file.WriteString("文件创建成功\n")
        log.Println("文件创建成功")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// os.Create 创建文件（已存在则清空）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Create</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">() </span><span style="color: #616E88">// defer 确保函数结束时关闭文件</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 写入内容验证创建成功</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">文件创建成功</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">文件创建成功</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong><code>defer file.Close()</code></strong> 是 Go 文件操作的最佳实践。<code>defer</code> 会确保即使后续代码出现 panic，文件也会被正确关闭，避免文件描述符泄漏。</p>
</blockquote>



<h2 class="wp-block-heading">2. 文件打开与关闭</h2>



<p class="wp-block-paragraph"><code>os</code> 包提供三种打开文件的方式，适用于不同场景：</p>



<figure class="wp-block-table reference"><table class="has-fixed-layout"><tbody><tr><th>函数</th><th>模式</th><th>说明</th></tr><tr><td><code>os.Open(name)</code></td><td>只读</td><td>最简单的方式，只读打开</td></tr><tr><td><code>os.Create(name)</code></td><td>读写 + 创建/截断</td><td>创建新文件或清空已有文件</td></tr><tr><td><code>os.OpenFile(name, flag, perm)</code></td><td>自定义</td><td>可指定读写、追加、创建等标志</td></tr></tbody></table></figure>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "os"
)

func main() {
        // 方式1：只读打开
        file, err := os.Open("example.txt")
        if err != nil {
                fmt.Println("打开失败:", err)
                return
        }
        defer file.Close()
        fmt.Println("文件打开成功")

        // 方式2：OpenFile 自定义模式
        // os.O_WRONLY  只写
        // os.O_CREATE  不存在则创建
        // os.O_APPEND  追加模式
        // os.O_TRUNC   存在则截断
        f, err := os.OpenFile("log.txt",
                os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
        if err != nil {
                fmt.Println("打开失败:", err)
                return
        }
        defer f.Close()
        f.WriteString("追加一行日志\n")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 方式1：只读打开</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Open</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">example.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">打开失败:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">文件打开成功</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 方式2：OpenFile 自定义模式</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// os.O_WRONLY  只写</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// os.O_CREATE  不存在则创建</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// os.O_APPEND  追加模式</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// os.O_TRUNC   存在则截断</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">f</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">OpenFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_WRONLY</span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_CREATE</span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_APPEND</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">打开失败:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">f</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">f</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">追加一行日志</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">OpenFile 标志位说明</h3>



<figure class="wp-block-table reference"><table class="has-fixed-layout"><tbody><tr><th>标志</th><th>说明</th></tr><tr><td><code>os.O_RDONLY</code></td><td>只读（默认）</td></tr><tr><td><code>os.O_WRONLY</code></td><td>只写</td></tr><tr><td><code>os.O_RDWR</code></td><td>读写</td></tr><tr><td><code>os.O_APPEND</code></td><td>追加模式，写入内容追加到文件末尾</td></tr><tr><td><code>os.O_CREATE</code></td><td>文件不存在时创建</td></tr><tr><td><code>os.O_TRUNC</code></td><td>打开时清空文件内容</td></tr><tr><td><code>os.O_EXCL</code></td><td>与 CREATE 配合，文件已存在时报错</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">3. 文件读取</h2>



<p class="wp-block-paragraph">Go 提供了三种主要的文件读取方式，根据文件大小和处理需求选择：<a href="https://www.atomic-cube.cn/wp-content/uploads/2026/04/c.png"><img decoding="async" class="alignnone size-full wp-image-1955" src="https://www.atomic-cube.cn/wp-content/uploads/2026/04/c.png" alt="" width="1956" height="484" srcset="https://www.atomic-cube.cn/wp-content/uploads/2026/04/c.png 1956w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/c-300x74.png 300w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/c-1024x253.png 1024w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/c-768x190.png 768w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/c-1536x380.png 1536w" sizes="(max-width: 1956px) 100vw, 1956px" /></a></p>



<h3 class="wp-block-heading">3.1 一次性读取（小文件推荐）</h3>



<p class="wp-block-paragraph"><code>os.ReadFile</code> 是最简洁的方式——自动打开、读取、关闭文件，一步到位：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "log"
        "os"
)

func main() {
        // 一次性读取整个文件（Go 1.16+）
        data, err := os.ReadFile("config.json")
        if err != nil {
                log.Fatal(err)
        }

        fmt.Println(string(data))
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 一次性读取整个文件（Go 1.16+）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">config.json</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">3.2 逐行读取（大文件推荐）</h3>



<p class="wp-block-paragraph">对于大文件，使用 <code>bufio.Scanner</code> 逐行处理，避免一次性加载到内存：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "bufio"
        "fmt"
        "log"
        "os"
)

func main() {
        file, err := os.Open("large-file.log")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 逐行扫描
        scanner := bufio.NewScanner(file)
        lineNum := 0
        for scanner.Scan() {
                lineNum++
                line := scanner.Text() // 获取当前行内容
                fmt.Printf("第 %d 行: %s\n", lineNum, line)
        }

        // 检查扫描过程中是否出错
        if err := scanner.Err(); err != nil {
                log.Fatal("读取错误:", err)
        }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">bufio</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Open</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">large-file.log</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 逐行扫描</span></span>
<span class="line"><span style="color: #D8DEE9FF">        scanner </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bufio</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewScanner</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        lineNum </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">scanner</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Scan</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">lineNum</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">                line </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">scanner</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Text</span><span style="color: #D8DEE9FF">() </span><span style="color: #616E88">// 获取当前行内容</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">第 %d 行: %s</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">lineNum</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">line</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 检查扫描过程中是否出错</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">scanner</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Err</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">读取错误:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>注意：</strong><code>bufio.Scanner</code> 默认最大行长度为 64KB。如果文件中有超长行，需要在调用 <code>Scan()</code> 前设置 <code>scanner.Buffer(make([]byte, 0), maxSize)</code> 来增大缓冲区。</p>
</blockquote>



<h3 class="wp-block-heading">3.3 使用 io.ReadAll 读取</h3>



<p class="wp-block-paragraph">当你已经有一个打开的 <code>io.Reader</code>（如网络响应、已打开的文件），可以使用 <code>io.ReadAll</code>：</p>



<pre class="wp-block-code"><code>package main

import (
        "fmt"
        "io"
        "log"
        "os"
)

func main() {
        file, err := os.Open("example.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 从 Reader 读取所有数据
        data, err := io.ReadAll(file)
        if err != nil {
                log.Fatal(err)
        }

        fmt.Println(string(data))
}</code></pre>



<h2 class="wp-block-heading">4. 文件写入</h2>



<p class="wp-block-paragraph">Go 提供了多种写入方式，从简单的一次性写入到高性能的缓冲写入：</p>



<h3 class="wp-block-heading">4.1 一次性写入</h3>



<p class="wp-block-paragraph"><code>os.WriteFile</code> 将数据一次性写入文件（覆盖原有内容）：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "log"
        "os"
)

func main() {
        content := []byte("Hello, Go!\n这是第二行\n")

        // 0644: 所有者读写，其他用户只读
        err := os.WriteFile("output.txt", content, 0644)
        if err != nil {
                log.Fatal(err)
        }
        log.Println("写入成功")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        content </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Hello, Go!</span><span style="color: #EBCB8B">\n</span><span style="color: #A3BE8C">这是第二行</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 0644: 所有者读写，其他用户只读</span></span>
<span class="line"><span style="color: #D8DEE9FF">        err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">output.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">content</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">写入成功</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">4.2 使用 File 对象写入</h3>



<p class="wp-block-paragraph">通过文件对象写入，可以分多次写入内容：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "log"
        "os"
)

func main() {
        file, err := os.Create("output.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 方式1：写入字符串
        file.WriteString("直接写入字符串\n")

        // 方式2：写入字节切片
        data := []byte("写入字节切片\n")
        file.Write(data)

        // 方式3：格式化写入
        fmt.Fprintf(file, "格式化写入: %d + %d = %d\n", 3, 4, 7)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Create</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">output.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 方式1：写入字符串</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">直接写入字符串</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 方式2：写入字节切片</span></span>
<span class="line"><span style="color: #D8DEE9FF">        data </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">写入字节切片</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Write</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 方式3：格式化写入</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fprintf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">格式化写入: %d + %d = %d</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">7</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">4.3 缓冲写入（大量数据推荐）</h3>



<p class="wp-block-paragraph"><code>bufio.Writer</code> 会先将数据写入内存缓冲区，攒够后再批量写入磁盘，显著减少 I/O 次数。<span class="marked">务必在结束前调用 <code>Flush()</code></span>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "bufio"
        "log"
        "os"
)

func main() {
        file, err := os.Create("buffered-output.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 创建带缓冲的写入器
        writer := bufio.NewWriter(file)

        // 写入多行（先进入缓冲区，不会立即写磁盘）
        for i := 0; i &lt; 1000; i++ {
                writer.WriteString("这是第 " + itoa(i) + " 行\n")
        }

        // Flush 将缓冲区剩余数据写入文件
        if err := writer.Flush(); err != nil {
                log.Fatal("刷新缓冲区失败:", err)
        }
        log.Println("缓冲写入完成")
}

// 简单的 int 转 string 辅助函数
func itoa(n int) string {
        return fmt.Sprintf("%d", n)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">bufio</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Create</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">buffered-output.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 创建带缓冲的写入器</span></span>
<span class="line"><span style="color: #D8DEE9FF">        writer </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bufio</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewWriter</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 写入多行（先进入缓冲区，不会立即写磁盘）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1000</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">writer</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">这是第 </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">itoa</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C"> 行</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Flush 将缓冲区剩余数据写入文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">writer</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Flush</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">刷新缓冲区失败:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">缓冲写入完成</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 简单的 int 转 string 辅助函数</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">itoa</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">n</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sprintf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">%d</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">n</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">5. 文件追加写入</h2>



<p class="wp-block-paragraph">使用 <code>os.O_APPEND</code> 标志在文件末尾追加内容，而不覆盖已有数据：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "log"
        "os"
        "time"
)

func main() {
        // O_APPEND: 追加模式
        // O_CREATE: 不存在则创建
        // O_WRONLY: 只写
        file, err := os.OpenFile("app.log",
                os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 每次运行都会追加，不会覆盖
        timestamp := time.Now().Format("2006-01-02 15:04:05")
        file.WriteString("&#91;" + timestamp + "&#93; 应用启动\n")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// O_APPEND: 追加模式</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// O_CREATE: 不存在则创建</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// O_WRONLY: 只写</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">OpenFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app.log</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_APPEND</span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_CREATE</span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_WRONLY</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 每次运行都会追加，不会覆盖</span></span>
<span class="line"><span style="color: #D8DEE9FF">        timestamp </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Now</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Format</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2006-01-02 15:04:05</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">&#91;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">&#93; 应用启动</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>对比：</strong><code>os.Create</code> 会清空已有内容；<code>os.OpenFile</code> 配合 <code>O_APPEND</code> 会追加内容。日志场景务必使用追加模式。</p>
</blockquote>



<h2 class="wp-block-heading">6. 文件删除与重命名</h2>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "log"
        "os"
)

func main() {
        // 重命名 / 移动文件
        err := os.Rename("old.txt", "new.txt")
        if err != nil {
                log.Fatal("重命名失败:", err)
        }
        log.Println("重命名成功")

        // 删除文件
        err = os.Remove("temp.txt")
        if err != nil {
                log.Fatal("删除失败:", err)
        }
        log.Println("删除成功")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 重命名 / 移动文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">        err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Rename</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">old.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">new.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">重命名失败:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">重命名成功</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 删除文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">temp.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">删除失败:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">删除成功</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">7. 文件信息与检查</h2>



<h3 class="wp-block-heading">7.1 获取文件信息</h3>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "log"
        "os"
)

func main() {
        info, err := os.Stat("test.txt")
        if err != nil {
                log.Fatal(err)
        }

        fmt.Println("文件名:    ", info.Name())
        fmt.Println("大小:      ", info.Size(), "字节")
        fmt.Println("权限:      ", info.Mode())
        fmt.Println("修改时间:  ", info.ModTime())
        fmt.Println("是目录吗:  ", info.IsDir())
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">文件名:    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">大小:      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Size</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">字节</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">权限:      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Mode</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">修改时间:  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ModTime</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">是目录吗:  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">7.2 检查文件是否存在</h3>



<p class="wp-block-paragraph">Go 没有专门的&#8221;文件存在&#8221;函数，通过 <code>os.Stat</code> + <code>os.IsNotExist</code> 判断：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "os"
)

// fileExists 检查文件是否存在
func fileExists(path string) bool {
        _, err := os.Stat(path)
        return !os.IsNotExist(err)
}

func main() {
        if fileExists("test.txt") {
                fmt.Println("文件存在")
        } else {
                fmt.Println("文件不存在")
        }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// fileExists 检查文件是否存在</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fileExists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsNotExist</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fileExists</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">文件存在</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">文件不存在</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">8. 目录操作</h2>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "log"
        "os"
)

func main() {
        // 创建单个目录
        err := os.Mkdir("newdir", 0755)
        if err != nil {
                log.Fatal(err)
        }

        // 递归创建多级目录（父目录不存在也会自动创建）
        err = os.MkdirAll("path/to/deep/dir", 0755)
        if err != nil {
                log.Fatal(err)
        }

        // 读取目录内容
        entries, err := os.ReadDir(".")
        if err != nil {
                log.Fatal(err)
        }
        for _, entry := range entries {
                mark := "&#x1f4c4;"
                if entry.IsDir() {
                        mark = "&#x1f4c1;"
                }
                fmt.Printf("%s %s\n", mark, entry.Name())
        }

        // 删除空目录
        os.Remove("newdir")

        // 递归删除目录及其所有内容（&#x26a0; 谨慎使用）
        os.RemoveAll("path")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 创建单个目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">        err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Mkdir</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">newdir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0755</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 递归创建多级目录（父目录不存在也会自动创建）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path/to/deep/dir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0755</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 读取目录内容</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">entries</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> entry </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entries</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                mark </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">📄</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">mark</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">📁</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">%s %s</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mark</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 删除空目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">newdir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 递归删除目录及其所有内容（⚠ 谨慎使用）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">RemoveAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">9. 高级操作</h2>



<h3 class="wp-block-heading">9.1 文件复制</h3>



<p class="wp-block-paragraph">使用 <code>io.Copy</code> 在两个文件之间高效复制数据：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "io"
        "log"
        "os"
)

// copyFile 复制文件的通用函数
func copyFile(src, dst string) (int64, error) {
        srcFile, err := os.Open(src)
        if err != nil {
                return 0, err
        }
        defer srcFile.Close()

        dstFile, err := os.Create(dst)
        if err != nil {
                return 0, err
        }
        defer dstFile.Close()

        // io.Copy 自动管理缓冲区，高效复制
        return io.Copy(dstFile, srcFile)
}

func main() {
        n, err := copyFile("source.txt", "destination.txt")
        if err != nil {
                log.Fatal(err)
        }
        log.Printf("复制完成，共 %d 字节", n)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">io</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// copyFile 复制文件的通用函数</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">copyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">int64</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Open</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Create</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// io.Copy 自动管理缓冲区，高效复制</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">io</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Copy</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">n</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">copyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">source.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">destination.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">复制完成，共 %d 字节</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">n</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">9.2 临时文件与目录</h3>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "log"
        "os"
)

func main() {
        // 创建临时文件（自动命名，前缀为 "app-"）
        tmpFile, err := os.CreateTemp("", "app-*.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer os.Remove(tmpFile.Name()) // 用完后清理
        fmt.Println("临时文件:", tmpFile.Name())

        // 创建临时目录
        tmpDir, err := os.MkdirTemp("", "app-*")
        if err != nil {
                log.Fatal(err)
        }
        defer os.RemoveAll(tmpDir) // 用完后清理
        fmt.Println("临时目录:", tmpDir)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 创建临时文件（自动命名，前缀为 &quot;app-&quot;）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">CreateTemp</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app-*.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">()) </span><span style="color: #616E88">// 用完后清理</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">临时文件:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 创建临时目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">tmpDir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirTemp</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app-*</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">RemoveAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpDir</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 用完后清理</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">临时目录:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpDir</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">9.3 递归遍历目录</h3>



<p class="wp-block-paragraph"><code>filepath.Walk</code> 可以递归遍历目录树中的所有文件和子目录：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
        "fmt"
        "log"
        "os"
        "path/filepath"
)

func main() {
        root := "." // 从当前目录开始遍历

        err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
                if err != nil {
                        return err // 遇到错误时可以选择跳过或返回
                }

                // 打印路径和大小
                size := ""
                if !info.IsDir() {
                        size = fmt.Sprintf(" (%d bytes)", info.Size())
                }
                fmt.Println(path + size)
                return nil
        })

        if err != nil {
                log.Fatal(err)
        }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path/filepath</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        root </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 从当前目录开始遍历</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Walk</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">FileInfo</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                if err != nil </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 遇到错误时可以选择跳过或返回</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// 打印路径和大小</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">size </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sprintf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C"> (%d bytes)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Size</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">9.4 跨平台路径拼接</h3>



<p class="wp-block-paragraph"><span class="marked">永远不要用字符串拼接路径</span>（如 <code>dir + "/" + file</code>），使用 <code>filepath.Join</code> 确保跨平台兼容：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// 错误：Windows 上路径分隔符是 \&lt;br>path := "dir" + "/" + "file.txt"&lt;br>&lt;br>// 正确：自动适配当前操作系统&lt;br>path := filepath.Join("dir", "file.txt")&lt;br>&lt;br></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// 错误：Windows 上路径分隔符是 \&lt;br&gt;path := &quot;dir&quot; + &quot;/&quot; + &quot;file.txt&quot;&lt;br&gt;&lt;br&gt;// 正确：自动适配当前操作系统&lt;br&gt;path := filepath.Join(&quot;dir&quot;, &quot;file.txt&quot;)&lt;br&gt;&lt;br&gt;</span></span></code></pre></div>



<h2 class="wp-block-heading">场景速查表</h2>



<figure class="wp-block-table reference"><table class="has-fixed-layout"><tbody><tr><th>场景</th><th>推荐方式</th><th>关键代码</th></tr><tr><td>读取小文件</td><td><code>os.ReadFile</code></td><td><code>data, err := os.ReadFile("file.txt")</code></td></tr><tr><td>逐行读取大文件</td><td><code>bufio.Scanner</code></td><td><code>scanner := bufio.NewScanner(file)</code></td></tr><tr><td>写入小文件</td><td><code>os.WriteFile</code></td><td><code>os.WriteFile("f.txt", data, 0644)</code></td></tr><tr><td>大量数据写入</td><td><code>bufio.Writer</code></td><td><code>writer := bufio.NewWriter(file)</code></td></tr><tr><td>追加日志</td><td><code>os.OpenFile</code> + <code>O_APPEND</code></td><td><code>os.OpenFile("log", os.O_APPEND|os.O_WRONLY, 0644)</code></td></tr><tr><td>文件复制</td><td><code>io.Copy</code></td><td><code>io.Copy(dst, src)</code></td></tr><tr><td>遍历目录</td><td><code>filepath.Walk</code></td><td><code>filepath.Walk(".", callback)</code></td></tr><tr><td>路径拼接</td><td><code>filepath.Join</code></td><td><code>filepath.Join("dir", "file.txt")</code></td></tr><tr><td>检查文件存在</td><td><code>os.Stat</code> + <code>os.IsNotExist</code></td><td><code>_, err := os.Stat(path)</code></td></tr><tr><td>创建临时文件</td><td><code>os.CreateTemp</code></td><td><code>os.CreateTemp("", "prefix-*.txt")</code></td></tr></tbody></table></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>文件权限说明：</strong><code>0644</code> 表示所有者可读写（6=4+2），组和其他用户只读（4）。目录通常使用 <code>0755</code>（多了执行权限，否则无法进入目录）。</p>
</blockquote>
<p>The post <a href="https://www.atomic-cube.cn/go-%e8%af%ad%e8%a8%80%e6%96%87%e4%bb%b6%e5%a4%84%e7%90%86/">Go 语言文件处理</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/go-%e8%af%ad%e8%a8%80%e6%96%87%e4%bb%b6%e5%a4%84%e7%90%86/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Go 文件和文件夹工具类：一个开箱即用的 fileutil 包v2</title>
		<link>https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85v2/</link>
					<comments>https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85v2/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[Gin]]></category>
		<category><![CDATA[go-zero]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1516</guid>

					<description><![CDATA[<p>基于 Go 1.25 的新特性，我将之前的工具包进行了全面重构和升级。 新版主要引入了&#160;os.Roo [&#8230;]</p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85v2/">Go 文件和文件夹工具类：一个开箱即用的 fileutil 包v2</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">基于 Go 1.25 的新特性，我将之前的工具包进行了全面重构和升级。</p>



<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="oVRA56Spjz"><a href="https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/">Go 文件和文件夹工具类：一个开箱即用的 fileutil 包</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="《 Go 文件和文件夹工具类：一个开箱即用的 fileutil 包 》—Zero to Inf" src="https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/embed/#?secret=jCvbdAJBoW#?secret=oVRA56Spjz" data-secret="oVRA56Spjz" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p class="wp-block-paragraph">新版主要引入了&nbsp;<code>os.Root</code>&nbsp;保障文件操作安全，使用迭代器和泛型提升代码的现代感与复用性，并整合了&nbsp;<code>testing/fstest</code>&nbsp;优化测试体验。</p>



<ul class="wp-block-list">
<li><strong>安全升级：<code>os.Root</code>&nbsp;防御路径穿越</strong>：使用 Go 1.24 引入的&nbsp;<code>os.Root</code>&nbsp;防范路径穿越攻击，安全地处理文件操作<a href="https://blog.csdn.net/EDDYCJY/article/details/152238656" target="_blank" rel="noreferrer noopener"></a>，同时也保留了不依赖此功能的传统方法<a href="https://www.tenable.com/plugins/nessus/301252" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>迭代器模式</strong>：利用 Go 1.23 引入的&nbsp;<code>iter.Seq</code>，为&nbsp;<code>ListFiles</code>&nbsp;等方法设计了迭代器版本，实现内存友好的流式处理<a href="https://golang.google.cn/doc/go1.25" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>泛型与类型安全</strong>：在迭代器和&nbsp;<code>BatchProcessor</code>&nbsp;中应用泛型，在编译时提供更强的类型安全保障。</li>



<li><strong>测试辅助</strong>：集成了&nbsp;<code>testing/fstest.MapFS</code>，方便构建基于内存的模拟文件系统进行单元测试。</li>



<li><strong>结构化管理与并发</strong>：引入&nbsp;<code>FileUtil</code>&nbsp;结构体封装基础路径，统一管理操作上下文。<code>BatchProcessor</code>&nbsp;结构体则用于对目录下的文件进行并发的批量处理。</li>
</ul>



<p class="wp-block-paragraph">一、新版&nbsp;<code>fileutil</code>&nbsp;包代码</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// Package fileutil 提供基于 Go 1.24+ 新特性的文件/目录操作工具。
package fileutil

import (
	"context"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"iter"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
)

// ============================================
// 1. 基础工具与常量
// ============================================

// Exists 判断路径是否存在。
func Exists(path string) bool {
	_, err := os.Stat(path)
	return !errors.Is(err, fs.ErrNotExist)
}

// IsDir 判断路径是否为目录。
func IsDir(path string) (bool, error) {
	info, err := os.Stat(path)
	if err != nil {
		return false, err
	}
	return info.IsDir(), nil
}

// IsFile 判断路径是否为普通文件。
func IsFile(path string) (bool, error) {
	info, err := os.Stat(path)
	if err != nil {
		return false, err
	}
	return !info.IsDir(), nil
}

// EnsureDir 确保目录存在，否则递归创建。
func EnsureDir(path string) error {
	if Exists(path) {
		isDir, err := IsDir(path)
		if err != nil {
			return err
		}
		if !isDir {
			return fmt.Errorf("path exists but is not a directory: %s", path)
		}
		return nil
	}
	return os.MkdirAll(path, 0755)
}

// ============================================
// 2. 核心结构：FileUtil (基于 os.Root)
// ============================================

// FileUtil 文件操作工具的核心结构体，封装了 os.Root。
type FileUtil struct {
	root *os.Root
	path string // 基础路径，用于日志或显示
}

// NewFileUtil 创建一个新的 FileUtil 实例。
// basePath 是所有后续操作受限的根目录。
func NewFileUtil(basePath string) (*FileUtil, error) {
	root, err := os.OpenRoot(basePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open root %q: %w", basePath, err)
	}
	return &amp;FileUtil{root: root, path: basePath}, nil
}

// Close 释放 FileUtil 持有的资源。
func (fu *FileUtil) Close() error {
	if fu.root != nil {
		return fu.root.Close()
	}
	return nil
}

// BasePath 返回 FileUtil 的根路径。
func (fu *FileUtil) BasePath() string {
	return fu.path
}

// ============================================
// 3. 安全的文件/目录操作 (使用 os.Root)
// ============================================

// SafeReadFile 安全地读取文件内容。
func (fu *FileUtil) SafeReadFile(relPath string) ([]byte, error) {
	return fu.root.ReadFile(relPath)
}

// SafeWriteFile 安全地将数据写入文件。
func (fu *FileUtil) SafeWriteFile(relPath string, data []byte, perm fs.FileMode) error {
	return fu.root.WriteFile(relPath, data, perm)
}

// SafeMkdirAll 安全地创建多级目录。
func (fu *FileUtil) SafeMkdirAll(relPath string, perm fs.FileMode) error {
	return fu.root.MkdirAll(relPath, perm)
}

// SafeRemove 安全地移除文件或空目录。
func (fu *FileUtil) SafeRemove(relPath string) error {
	return fu.root.Remove(relPath)
}

// ============================================
// 4. 传统文件/目录操作 (不使用 os.Root)
// ============================================

// CopyFile 复制文件从 src 到 dst。
func CopyFile(src, dst string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("open source file: %w", err)
	}
	defer srcFile.Close()

	srcInfo, err := srcFile.Stat()
	if err != nil {
		return fmt.Errorf("stat source file: %w", err)
	}

	dstFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("create destination file: %w", err)
	}
	defer dstFile.Close()

	if _, err := io.Copy(dstFile, srcFile); err != nil {
		return fmt.Errorf("copy content: %w", err)
	}

	if err := dstFile.Sync(); err != nil {
		return fmt.Errorf("sync destination file: %w", err)
	}
	return os.Chmod(dst, srcInfo.Mode())
}

// MoveFile 移动文件。
func MoveFile(src, dst string) error {
	err := os.Rename(src, dst)
	if err == nil {
		return nil
	}
	// 跨文件系统降级处理
	if err := CopyFile(src, dst); err != nil {
		return fmt.Errorf("copy file during move: %w", err)
	}
	return os.Remove(src)
}

// CopyDir 递归复制整个目录。
func CopyDir(src, dst string) error {
	srcInfo, err := os.Stat(src)
	if err != nil {
		return fmt.Errorf("stat source directory: %w", err)
	}
	if !srcInfo.IsDir() {
		return fmt.Errorf("source is not a directory: %s", src)
	}

	if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
		return fmt.Errorf("create destination directory: %w", err)
	}

	entries, err := os.ReadDir(src)
	if err != nil {
		return fmt.Errorf("read source directory: %w", err)
	}

	for _, entry := range entries {
		srcPath := filepath.Join(src, entry.Name())
		dstPath := filepath.Join(dst, entry.Name())
		if entry.IsDir() {
			if err := CopyDir(srcPath, dstPath); err != nil {
				return err
			}
		} else {
			if err := CopyFile(srcPath, dstPath); err != nil {
				return err
			}
		}
	}
	return nil
}

// DeleteDir 删除整个目录 (类似 rm -rf)。
func DeleteDir(path string) error {
	if !Exists(path) {
		return nil
	}
	return os.RemoveAll(path)
}

// ReadString 快速将文件内容读取为字符串。
func ReadString(path string) (string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

// WriteString 快速将字符串写入文件。
func WriteString(path, content string) error {
	return os.WriteFile(path, []byte(content), 0644)
}

// ============================================
// 5. 迭代器与流式处理
// ============================================

// Lines 返回一个迭代器，用于逐行读取文件内容。
func Lines(path string) (iter.Seq&#91;string&#93;, func() error) {
	file, err := os.Open(path)
	if err != nil {
		return func(yield func(string) bool) {
			return
		}, func() error { return err }
	}

	return func(yield func(string) bool) {
			defer file.Close()
			scanner := bufio.NewScanner(file)
			for scanner.Scan() {
				if !yield(scanner.Text()) {
					return
				}
			}
		}, func() error {
			defer file.Close()
			scanner := bufio.NewScanner(file)
			for scanner.Scan() {
				// 消耗迭代器以检查错误
			}
			return scanner.Err()
		}
}

// ListFilesIter 返回一个迭代器，流式地遍历目录下的文件。
func ListFilesIter(dir string, extensions []string) (iter.Seq&#91;string&#93;, func() error) {
	var walkErr error
	seq := func(yield func(string) bool) {
		walkErr = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
			if err != nil {
				return err
			}
			if d.IsDir() {
				return nil
			}
			if len(extensions) > 0 {
				ext := strings.ToLower(filepath.Ext(path))
				matched := false
				for _, e := range extensions {
					if strings.ToLower(e) == ext {
						matched = true
						break
					}
				}
				if !matched {
					return nil
				}
			}
			if !yield(path) {
				return filepath.SkipAll
			}
			return nil
		})
	}
	return seq, func() error { return walkErr }
}

// DirSize 计算目录总大小。
func DirSize(path string) (int64, error) {
	var size int64
	err := filepath.WalkDir(path, func(_ string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if !d.IsDir() {
			info, err := d.Info()
			if err != nil {
				return err
			}
			size += info.Size()
		}
		return nil
	})
	return size, err
}

// ============================================
// 6. 原子写入与批量处理
// ============================================

// SafeWrite 原子性地将数据写入文件。
func SafeWrite(path string, data []byte, perm fs.FileMode) error {
	dir, filename := filepath.Split(path)
	tmpFile, err := os.CreateTemp(dir, ".tmp-*"+filename)
	if err != nil {
		return err
	}
	tmpName := tmpFile.Name()
	defer os.Remove(tmpName)

	if _, err := tmpFile.Write(data); err != nil {
		tmpFile.Close()
		return err
	}
	if err := tmpFile.Sync(); err != nil {
		tmpFile.Close()
		return err
	}
	if err := tmpFile.Close(); err != nil {
		return err
	}
	if err := os.Chmod(tmpName, perm); err != nil {
		return err
	}
	return os.Rename(tmpName, path)
}

// BatchProcessor 用于并发处理目录下的一系列文件。
type BatchProcessor&#91;T any&#93; struct {
	workers int
}

// NewBatchProcessor 创建一个新的批量处理器。
func NewBatchProcessor&#91;T any&#93;(workers int) *BatchProcessor&#91;T&#93; {
	if workers &lt;= 0 {
		workers = runtime.NumCPU()
	}
	return &amp;BatchProcessor&#91;T&#93;{workers: workers}
}

// Process 对目录 dir 下匹配扩展名的所有文件，并发执行处理函数。
func (bp *BatchProcessor&#91;T&#93;) Process(ctx context.Context, dir string, extensions []string, handler func(string) (T, error)) ([]T, []error) {
	fileSeq, errFn := ListFilesIter(dir, extensions)
	var wg sync.WaitGroup
	fileCh := make(chan string)
	resultCh := make(chan T)
	errCh := make(chan error)

	// 启动 workers
	for i := 0; i &lt; bp.workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for file := range fileCh {
				select {
				case &lt;-ctx.Done():
					return
				default:
					if res, err := handler(file); err != nil {
						errCh &lt;- fmt.Errorf("file %s: %w", file, err)
					} else {
						resultCh &lt;- res
					}
				}
			}
		}()
	}

	// 发送文件路径
	go func() {
		for file := range fileSeq {
			select {
			case &lt;-ctx.Done():
				break
			case fileCh &lt;- file:
			}
		}
		close(fileCh)
	}()

	// 等待并收集结果
	go func() {
		wg.Wait()
		close(resultCh)
		close(errCh)
	}()

	var results []T
	var errs []error
	for {
		select {
		case res, ok := &lt;-resultCh:
			if !ok {
				resultCh = nil
			} else {
				results = append(results, res)
			}
		case err, ok := &lt;-errCh:
			if !ok {
				errCh = nil
			} else {
				errs = append(errs, err)
			}
		}
		if resultCh == nil &amp;&amp; errCh == nil {
			break
		}
	}

	if walkErr := errFn(); walkErr != nil {
		errs = append(errs, fmt.Errorf("walk error: %w", walkErr))
	}

	return results, errs
}

// ============================================
// 7. 测试辅助 (testing/fstest)
// ============================================

// NewTestFS 创建一个用于测试的内存文件系统。
func NewTestFS() fstest.MapFS {
	return make(fstest.MapFS)
}

// AddTestFile 向测试文件系统添加一个文件。
func AddTestFile(fsys fstest.MapFS, path, content string) {
	fsys&#91;path&#93; = &amp;fstest.MapFile{Data: []byte(content)}
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// Package fileutil 提供基于 Go 1.24+ 新特性的文件/目录操作工具。</span></span>
<span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">errors</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">io</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">io/fs</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">iter</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path/filepath</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">runtime</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">strings</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 1. 基础工具与常量</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Exists 判断路径是否存在。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Is</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fs</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ErrNotExist</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// IsDir 判断路径是否为目录。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// IsFile 判断路径是否为普通文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">IsFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// EnsureDir 确保目录存在，否则递归创建。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">EnsureDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">isDir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">isDir</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path exists but is not a directory: %s</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0755</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 2. 核心结构：FileUtil (基于 os.Root)</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// FileUtil 文件操作工具的核心结构体，封装了 os.Root。</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> FileUtil struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	root *os.Root</span></span>
<span class="line"><span style="color: #D8DEE9FF">	path string </span><span style="color: #616E88">// 基础路径，用于日志或显示</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// NewFileUtil 创建一个新的 FileUtil 实例。</span></span>
<span class="line"><span style="color: #616E88">// basePath 是所有后续操作受限的根目录。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewFileUtil</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">basePath</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">OpenRoot</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">basePath</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">failed to open root %q: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">basePath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">root</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">path</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">basePath</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Close 释放 FileUtil 持有的资源。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">fu</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">root</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// BasePath 返回 FileUtil 的根路径。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">fu</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">BasePath</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">path</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 3. 安全的文件/目录操作 (使用 os.Root)</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// SafeReadFile 安全地读取文件内容。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">fu</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">SafeReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) ([]</span><span style="color: #D8DEE9">byte</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// SafeWriteFile 安全地将数据写入文件。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">fu</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">SafeWriteFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">byte</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fs</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">FileMode</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// SafeMkdirAll 安全地创建多级目录。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">fu</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">SafeMkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fs</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">FileMode</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// SafeRemove 安全地移除文件或空目录。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">fu</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">FileUtil</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">SafeRemove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">root</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">relPath</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 4. 传统文件/目录操作 (不使用 os.Root)</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// CopyFile 复制文件从 src 到 dst。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Open</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">open source file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">stat source file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Create</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">create destination file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">io</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Copy</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">copy content: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sync</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync destination file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Chmod</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Mode</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MoveFile 移动文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">MoveFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Rename</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 跨文件系统降级处理</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">copy file during move: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// CopyDir 递归复制整个目录。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">stat source directory: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">source is not a directory: %s</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Mode</span><span style="color: #D8DEE9FF">())</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">create destination directory: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">entries</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">read source directory: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> entry </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entries</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		srcPath </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Join</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">		dstPath </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Join</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">srcPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstPath</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">srcPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstPath</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// DeleteDir 删除整个目录 (类似 rm -rf)。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DeleteDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">RemoveAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ReadString 快速将文件内容读取为字符串。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ReadString</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WriteString 快速将字符串写入文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 5. 迭代器与流式处理</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Lines 返回一个迭代器，用于逐行读取文件内容。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Lines</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">iter</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Seq</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Open</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">yield</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">bool</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> return </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">yield</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">bool</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			defer file.Close()</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #88C0D0">scanner </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bufio</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewScanner</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">scanner</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Scan</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				if !</span><span style="color: #88C0D0">yield</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">scanner.Text(</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			defer file.Close()</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #88C0D0">scanner </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bufio</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewScanner</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">scanner</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Scan</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">				</span><span style="color: #616E88">// 消耗迭代器以检查错误</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">scanner</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Err</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ListFilesIter 返回一个迭代器，流式地遍历目录下的文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ListFilesIter</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dir</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">extensions</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">iter</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Seq</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">walkErr</span><span style="color: #D8DEE9FF"> error</span></span>
<span class="line"><span style="color: #D8DEE9FF">	seq </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">yield</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">bool</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">walkErr</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WalkDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fs</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">DirEntry</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			if err != nil </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			if d.IsDir() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			if </span><span style="color: #88C0D0">len</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">extensions</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> &gt; 0 </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				ext </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">strings</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ToLower</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Ext</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">				matched </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> e </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">extensions</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">strings</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ToLower</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">e</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ext</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">						</span><span style="color: #D8DEE9">matched</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">						</span><span style="color: #81A1C1">break</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">matched</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			if !</span><span style="color: #88C0D0">yield</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">SkipAll</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			return </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">seq</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> return </span><span style="color: #D8DEE9">walkErr</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// DirSize 计算目录总大小。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DirSize</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">int64</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF"> int64</span></span>
<span class="line"><span style="color: #D8DEE9FF">	err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WalkDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fs</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">DirEntry</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		if err != nil </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		if !d.IsDir() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Info</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Size</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		return </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">size</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 6. 原子写入与批量处理</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// SafeWrite 原子性地将数据写入文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">SafeWrite</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">byte</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fs</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">FileMode</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">dir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> filename </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Split</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">CreateTemp</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.tmp-*</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9">filename</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	tmpName </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Write</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sync</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Chmod</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Rename</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// BatchProcessor 用于并发处理目录下的一系列文件。</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> BatchProcessor&#91;T any&#93; struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	workers int</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// NewBatchProcessor 创建一个新的批量处理器。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NewBatchProcessor</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">any</span><span style="color: #D8DEE9FF">&#93;(</span><span style="color: #D8DEE9">workers</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">BatchProcessor</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">&#93; </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">workers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">workers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">runtime</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NumCPU</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">BatchProcessor</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">workers</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">workers</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Process 对目录 dir 下匹配扩展名的所有文件，并发执行处理函数。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">bp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">BatchProcessor</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">&#93;) </span><span style="color: #88C0D0">Process</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dir</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">extensions</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">handler</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">T</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">)) ([]</span><span style="color: #D8DEE9">T</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fileSeq</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> errFn </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ListFilesIter</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">extensions</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> sync.WaitGroup</span></span>
<span class="line"><span style="color: #D8DEE9FF">	fileCh </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	resultCh </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	errCh </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 启动 workers</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bp</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">workers</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> file </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">():</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">res</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handler</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">						</span><span style="color: #D8DEE9">errCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">file %s: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">						</span><span style="color: #D8DEE9">resultCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">res</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 发送文件路径</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> file </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileSeq</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">():</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">break</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> file</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">fileCh</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 等待并收集结果</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">resultCh</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">errCh</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF"> []T</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errs</span><span style="color: #D8DEE9FF"> []error</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">res</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> ok </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF">resultCh</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">resultCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">append</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">results</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">res</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> ok </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF">errCh</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">errCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">errs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">append</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">errs</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">resultCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errCh</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">break</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> walkErr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">errFn</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">walkErr</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">errs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">append</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">errs</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">walk error: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">walkErr</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">results</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errs</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"><span style="color: #616E88">// 7. 测试辅助 (testing/fstest)</span></span>
<span class="line"><span style="color: #616E88">// ============================================</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// NewTestFS 创建一个用于测试的内存文件系统。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewTestFS</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">fstest</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">MapFS</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">fstest</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">MapFS</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// AddTestFile 向测试文件系统添加一个文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AddTestFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">fsys</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fstest</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">MapFS</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fsys</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">&#93; </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">fstest</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">MapFile</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">Data</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">二、使用示例</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>func main() {
	// 1. 使用 os.Root 进行安全操作
	fu, err := fileutil.NewFileUtil("./data")
	if err != nil {
		log.Fatal(err)
	}
	defer fu.Close()

	if err := fu.SafeMkdirAll("logs", 0755); err != nil {
		log.Fatal(err)
	}
	
	if err := fu.SafeWriteFile("config.json", []byte(`{"key": "value"}`), 0644); err != nil {
		log.Fatal(err)
	}
	
	data, err := fu.SafeReadFile("config.json")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(data))

	// 2. 使用迭代器逐行读取
	linesSeq, errFn := fileutil.Lines("./data/large.log")
	var count int
	for line := range linesSeq {
		_ = line
		count++
	}
	if err := errFn(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Total lines: %d\n", count)

	// 3. 批量处理文件
	bp := fileutil.NewBatchProcessor&#91;string&#93;(4)
	results, errs := bp.Process(context.Background(), "./data", []string{".log"}, func(path string) (string, error) {
		return fileutil.ReadString(path)
	})
	for _, err := range errs {
		log.Printf("Error: %v", err)
	}
	fmt.Printf("Processed %d files\n", len(results))
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 1. 使用 os.Root 进行安全操作</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewFileUtil</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">SafeMkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">logs</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0755</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">SafeWriteFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">config.json</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">{&quot;key&quot;: &quot;value&quot;}</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">SafeReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">config.json</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 2. 使用迭代器逐行读取</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">linesSeq</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> errFn </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lines</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/large.log</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">count</span><span style="color: #D8DEE9FF"> int</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> line </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">linesSeq</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">line</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">count</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">errFn</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Total lines: %d</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">count</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 3. 批量处理文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">	bp </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">NewBatchProcessor</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">&#93;(</span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">results</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> errs </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bp</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Process</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.log</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		return fileutil.ReadString(path)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errs</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Error: %v</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Processed %d files</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">len</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">三、关键优化点详解</h3>



<ol start="1" class="wp-block-list">
<li><strong><code>os.Root</code>：安全的文件操作基础</strong><br>通过&nbsp;<code>os.OpenRoot(basePath)</code>&nbsp;创建一个安全的根，后续所有操作（如&nbsp;<code>ReadFile</code>）都会自动被限制在这个根目录内。这从根本上杜绝了路径穿越攻击，是处理来自用户输入的文件路径时最推荐的做法<a href="https://blog.csdn.net/EDDYCJY/article/details/152238656" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>迭代器：内存友好的流式处理</strong><br>对于大文件或包含海量文件的目录，一次性加载所有内容可能导致内存溢出。迭代器模式允许你以流式方式逐行或逐个文件地处理数据，例如&nbsp;<code>Lines</code>&nbsp;函数逐行返回内容，内存占用极低。</li>



<li><strong>泛型批处理器：复用并发处理逻辑</strong><br><code>BatchProcessor[T]</code>&nbsp;是一个通用的批处理器，它封装了并发遍历、处理文件并收集结果的复杂逻辑。你只需指定返回类型&nbsp;<code>T</code>&nbsp;和核心处理函数，而无需关心&nbsp;<code>goroutine</code>&nbsp;管理、<code>channel</code>&nbsp;同步等细节。</li>



<li><strong><code>testing/fstest</code>：轻量级单元测试</strong><br>传统的文件系统单元测试往往依赖真实文件，过程繁琐且易出错。<code>testing/fstest</code>&nbsp;允许在内存中轻松构建一个模拟的文件系统，让测试变得快速、可靠且易于管理。</li>
</ol>



<p class="wp-block-paragraph">希望这个融合了 Go 新特性的工具包能让你的开发工作更高效、更安全。</p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85v2/">Go 文件和文件夹工具类：一个开箱即用的 fileutil 包v2</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85v2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title> Go 开发经验总结：从入门到写出生产级代码</title>
		<link>https://www.atomic-cube.cn/go-%e5%bc%80%e5%8f%91%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%93%ef%bc%9a%e4%bb%8e%e5%85%a5%e9%97%a8%e5%88%b0%e5%86%99%e5%87%ba%e7%94%9f%e4%ba%a7%e7%ba%a7%e4%bb%a3%e7%a0%81/</link>
					<comments>https://www.atomic-cube.cn/go-%e5%bc%80%e5%8f%91%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%93%ef%bc%9a%e4%bb%8e%e5%85%a5%e9%97%a8%e5%88%b0%e5%86%99%e5%87%ba%e7%94%9f%e4%ba%a7%e7%ba%a7%e4%bb%a3%e7%a0%81/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Sat, 18 Apr 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[Gin]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1455</guid>

					<description><![CDATA[<p>用 Go 语言写后端已经好多年了。从最初被&#160;if err != nil&#160;烦到不行，到现在能 [&#8230;]</p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e5%bc%80%e5%8f%91%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%93%ef%bc%9a%e4%bb%8e%e5%85%a5%e9%97%a8%e5%88%b0%e5%86%99%e5%87%ba%e7%94%9f%e4%ba%a7%e7%ba%a7%e4%bb%a3%e7%a0%81/"> Go 开发经验总结：从入门到写出生产级代码</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">用 Go 语言写后端已经好多年了。从最初被&nbsp;<code>if err != nil</code>&nbsp;烦到不行，到现在能熟练应对各种并发场景和代码组织，踩过不少坑，也有一些心得。这篇文章把这些经验整理下来，希望能帮到正在用 Go 的朋友。每个主题都会配上可直接运行的代码示例，你可以边看边跑。</p>



<h2 class="wp-block-heading">1. 错误处理：不止是&nbsp;<code>if err != nil</code></h2>



<p class="wp-block-paragraph">很多刚接触 Go 的人都会被满屏的&nbsp;<code>if err != nil</code>&nbsp;吓到。但真正的问题是，很多人只检查了错误，却没做好错误包装和分类。</p>



<h3 class="wp-block-heading">1.1 用&nbsp;<code>%w</code>&nbsp;包装错误，保留上下文</h3>



<p class="wp-block-paragraph">Go 1.13 引入的错误包装功能非常实用。使用&nbsp;<code>fmt.Errorf</code>&nbsp;的&nbsp;<code>%w</code>&nbsp;动词，可以在每一层添加上下文信息，同时保留原始错误，方便上层用&nbsp;<code>errors.Is</code>&nbsp;和&nbsp;<code>errors.As</code>&nbsp;判断：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "errors"
    "fmt"
)

// 定义业务错误常量
var ErrNotFound = errors.New("not found")

// 模拟底层操作
func fetchFromDB(id int) (string, error) {
    return "", ErrNotFound
}

// 中间层：包装错误，添加上下文
func getUser(id int) (string, error) {
    name, err := fetchFromDB(id)
    if err != nil {
        return "", fmt.Errorf("get user %d: %w", id, err)
    }
    return name, nil
}

func main() {
    _, err := getUser(42)
    if err != nil {
        // 使用 errors.Is 判断错误类型
        if errors.Is(err, ErrNotFound) {
            fmt.Printf("业务判断：用户不存在\n")
        }
        // 完整错误信息包含所有上下文
        fmt.Printf("完整错误：%v\n", err)
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">errors</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 定义业务错误常量</span></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrNotFound</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">not found</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 模拟底层操作</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetchFromDB</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrNotFound</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 中间层：包装错误，添加上下文</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getUser</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">name</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetchFromDB</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">get user %d: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">name</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getUser</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 使用 errors.Is 判断错误类型</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Is</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrNotFound</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">业务判断：用户不存在</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 完整错误信息包含所有上下文</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">完整错误：%v</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">输出：<br>业务判断：用户不存在<br>完整错误：get user 42: not found</p>



<p class="wp-block-paragraph">这种包装方式让错误信息像堆栈一样层层递进，排查问题时会省很多时间<a href="https://cloud.tencent.cn/developer/article/2575113?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</p>



<h3 class="wp-block-heading">1.2 只在不可恢复时使用 panic</h3>



<p class="wp-block-paragraph">Go 社区有共识：panic 只应在程序无法继续运行时使用，比如启动时依赖的配置加载失败。业务逻辑中应该用错误返回值，让调用方决定如何处理<a href="https://cloud.tencent.cn/developer/article/2506310?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// ❌ 错误：滥用 panic
func readConfig() string {
    data, err := os.ReadFile("config.yaml")
    if err != nil {
        panic(err)  // 直接崩溃，调用方无法处理
    }
    return string(data)
}

// ✅ 正确：返回错误
func readConfig() (string, error) {
    data, err := os.ReadFile("config.yaml")
    if err != nil {
        return "", fmt.Errorf("read config: %w", err)
    }
    return string(data), nil
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// ❌ 错误：滥用 panic</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">readConfig</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">config.yaml</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">panic</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)  </span><span style="color: #616E88">// 直接崩溃，调用方无法处理</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ✅ 正确：返回错误</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">readConfig</span><span style="color: #D8DEE9FF">() (</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">config.yaml</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">read config: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">2. 并发编程：goroutine 的正确打开方式</h2>



<h3 class="wp-block-heading">2.1 用 Context 控制 goroutine 生命周期</h3>



<p class="wp-block-paragraph">在 HTTP 服务或长时间运行的任务中，用 context 来控制 goroutine 的启动和停止是最标准的做法<a href="https://cloud.tencent.com.cn/developer/article/2566540?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：</p>



<p class="wp-block-paragraph">Go 的并发模型很简洁，但用不好容易出 goroutine 泄漏、数据竞争等问题。</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case &lt;-ctx.Done():
            fmt.Printf("worker %d 收到取消信号，退出\n", id)
            return
        default:
            fmt.Printf("worker %d 工作中...\n", id)
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    // 启动 3 个 worker
    for i := 1; i &lt;= 3; i++ {
        go worker(ctx, i)
    }

    // 5 秒后取消所有 worker
    time.Sleep(5 * time.Second)
    cancel()

    // 给 worker 一点时间退出
    time.Sleep(500 * time.Millisecond)
    fmt.Println("main 退出")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">worker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">():</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">worker %d 收到取消信号，退出</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">worker %d 工作中...</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithCancel</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 启动 3 个 worker</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">worker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 5 秒后取消所有 worker</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 给 worker 一点时间退出</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">main 退出</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">2.2 Channel + Actor 模型：通信共享内存，而非共享内存通信</h3>



<p class="wp-block-paragraph">Go 的哲学是“不要通过共享内存来通信，而要通过通信来共享内存”。把共享状态封装在一个 goroutine 内，通过 channel 传递操作指令，可以彻底避免数据竞争<a href="https://cloud.tencent.cn/developer/article/2562803" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "time"
)

// Counter 将计数状态封装在独立的 goroutine 中
type Counter struct {
    ops chan func(*int) // 操作函数队列
}

func NewCounter() *Counter {
    c := &amp;Counter{
        ops: make(chan func(*int)),
    }
    go c.loop()
    return c
}

func (c *Counter) loop() {
    var value int
    for op := range c.ops {
        op(&amp;value)
    }
}

// Increment 增加计数
func (c *Counter) Increment() {
    done := make(chan struct{})
    c.ops &lt;- func(v *int) {
        *v++
        close(done)
    }
    &lt;-done // 等待操作完成
}

// Value 获取当前值
func (c *Counter) Value() int {
    done := make(chan int)
    c.ops &lt;- func(v *int) {
        done &lt;- *v
    }
    return &lt;-done
}

func main() {
    counter := NewCounter()

    // 并发增加计数
    for i := 0; i &lt; 100; i++ {
        go counter.Increment()
    }

    time.Sleep(100 * time.Millisecond)
    fmt.Printf("最终计数: %d\n", counter.Value())
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Counter 将计数状态封装在独立的 goroutine 中</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Counter struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ops chan func(*int) </span><span style="color: #616E88">// 操作函数队列</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewCounter</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Counter</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    c </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">Counter</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        ops</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">))</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">loop</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Counter</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">loop</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> op </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ops</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">op</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Increment 增加计数</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Counter</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Increment</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    done </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">struct</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ops</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">v</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">done</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">done</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 等待操作完成</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Value 获取当前值</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Counter</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Value</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    done </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ops</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">done</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">v</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">done</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    counter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewCounter</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 并发增加计数</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">counter</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Increment</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">100</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">最终计数: %d</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">counter</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Value</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">这种模式本质上是 Actor 模型的 Go 实现，完全不用锁，代码也清晰易读<a href="https://cloud.tencent.cn/developer/article/2562803" target="_blank" rel="noreferrer noopener"></a>。</p>



<h3 class="wp-block-heading">2.3 Worker Pool 模式</h3>



<p class="wp-block-paragraph">当有大量任务需要并发处理时，用 worker pool 控制并发数可以避免资源耗尽：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs &lt;-chan int, results chan&lt;- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("worker %d 处理任务 %d\n", id, job)
        time.Sleep(500 * time.Millisecond) // 模拟耗时操作
        results &lt;- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    var wg sync.WaitGroup

    // 启动 worker 池
    for w := 1; w &lt;= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &amp;wg)
    }

    // 发送任务
    for j := 1; j &lt;= numJobs; j++ {
        jobs &lt;- j
    }
    close(jobs)

    // 等待所有 worker 完成
    go func() {
        wg.Wait()
        close(results)
    }()

    // 收集结果
    for res := range results {
        fmt.Printf("结果: %d\n", res)
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">worker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">jobs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">chan</span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">WaitGroup</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> job </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">jobs</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">worker %d 处理任务 %d</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">job</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 模拟耗时操作</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">job</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numJobs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numWorkers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    jobs </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numJobs</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    results </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numJobs</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> sync.WaitGroup</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 启动 worker 池</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> w </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">w</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numWorkers</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">w</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">worker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">w</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">jobs</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">results</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 发送任务</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> j </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">j</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numJobs</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">j</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">jobs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">j</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">jobs</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 等待所有 worker 完成</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 收集结果</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> res </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">results</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">结果: %d</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">res</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">3. 项目结构：告别代码混乱</h2>



<p class="wp-block-paragraph">随着项目变大，代码组织会成为头痛的问题。业界普遍参考 Standard Go Project Layout，但不必全盘照搬，可以按需取用<a href="https://blog.csdn.net/gitblog_00081/article/details/151199502" target="_blank" rel="noreferrer noopener"></a>。</p>



<p class="wp-block-paragraph">推荐一个中小型项目的结构：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>myproject/
├── cmd/
│   └── api/
│       └── main.go          # 应用入口，尽量精简
├── internal/
│   ├── handler/             # HTTP 处理器
│   │   └── user.go
│   ├── service/             # 业务逻辑
│   │   └── user.go
│   ├── repository/          # 数据访问
│   │   └── user.go
│   └── model/               # 数据模型
│       └── user.go
├── pkg/                     # 可被外部引用的公共库
│   └── utils/
├── go.mod
└── go.sum</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">myproject</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">├── </span><span style="color: #D8DEE9">cmd</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   └── </span><span style="color: #D8DEE9">api</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">│       └── </span><span style="color: #D8DEE9">main</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF">          # </span><span style="color: #D8DEE9">应用入口</span><span style="color: #D8DEE9FF">，</span><span style="color: #D8DEE9">尽量精简</span></span>
<span class="line"><span style="color: #D8DEE9FF">├── </span><span style="color: #D8DEE9">internal</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   ├── </span><span style="color: #D8DEE9">handler</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">             # </span><span style="color: #D8DEE9">HTTP</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">处理器</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   │   └── </span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   ├── </span><span style="color: #D8DEE9">service</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">             # </span><span style="color: #D8DEE9">业务逻辑</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   │   └── </span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   ├── </span><span style="color: #D8DEE9">repository</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">          # </span><span style="color: #D8DEE9">数据访问</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   │   └── </span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   └── </span><span style="color: #D8DEE9">model</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">               # </span><span style="color: #D8DEE9">数据模型</span></span>
<span class="line"><span style="color: #D8DEE9FF">│       └── </span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span></span>
<span class="line"><span style="color: #D8DEE9FF">├── </span><span style="color: #D8DEE9">pkg</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">                     # </span><span style="color: #D8DEE9">可被外部引用的公共库</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   └── </span><span style="color: #D8DEE9">utils</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">├── </span><span style="color: #D8DEE9">go</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mod</span></span>
<span class="line"><span style="color: #D8DEE9FF">└── </span><span style="color: #D8DEE9">go</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">sum</span></span></code></pre></div>



<p class="wp-block-paragraph">要点：</p>



<ul class="wp-block-list">
<li><code>cmd/</code>&nbsp;下放&nbsp;<code>main.go</code>，每个子目录对应一个可执行程序</li>



<li><code>internal/</code>&nbsp;下的代码只有本项目能引用，Go 编译器会强制保证这一点</li>



<li><code>pkg/</code>&nbsp;放可以被其他项目引用的公共代码</li>



<li>main 函数尽量简短，业务逻辑都放在&nbsp;<code>internal</code>&nbsp;中</li>
</ul>



<p class="wp-block-paragraph"><code>internal/</code>&nbsp;内部按功能分层是一个经典做法，但如果你偏好 Clean Architecture，也可以按领域组织：<br></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>internal/
├── user/           # 用户领域的所有代码
│   ├── delivery.go  # HTTP handler
│   ├── service.go   # 业务逻辑
│   └── repository.go # 数据层
└── order/          # 订单领域
    └── ...</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">internal</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">├── </span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">           # </span><span style="color: #D8DEE9">用户领域的所有代码</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   ├── </span><span style="color: #D8DEE9">delivery</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF">  # </span><span style="color: #D8DEE9">HTTP</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">handler</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   ├── </span><span style="color: #D8DEE9">service</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF">   # </span><span style="color: #D8DEE9">业务逻辑</span></span>
<span class="line"><span style="color: #D8DEE9FF">│   └── </span><span style="color: #D8DEE9">repository</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> # </span><span style="color: #D8DEE9">数据层</span></span>
<span class="line"><span style="color: #D8DEE9FF">└── </span><span style="color: #D8DEE9">order</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">          # </span><span style="color: #D8DEE9">订单领域</span></span>
<span class="line"><span style="color: #D8DEE9FF">    └── </span><span style="color: #81A1C1">...</span></span></code></pre></div>



<p class="wp-block-paragraph">选择哪种结构取决于团队习惯和项目规模，关键是保持一致。</p>



<h2 class="wp-block-heading">4. Gin 框架：快速构建 Web 服务</h2>



<p class="wp-block-paragraph">Gin 是 Go 生态里使用最广泛的 Web 框架之一，性能好，中间件机制也很方便。</p>



<h3 class="wp-block-heading">4.1 基础用法</h3>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    r := gin.Default()

    // 路由分组
    v1 := r.Group("/api/v1")
    {
        v1.GET("/ping", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "pong"})
        })

        v1.GET("/users/:id", func(c *gin.Context) {
            id := c.Param("id")
            c.JSON(http.StatusOK, User{ID: 1, Name: "张三"})
        })

        v1.POST("/users", func(c *gin.Context) {
            var user User
            if err := c.ShouldBindJSON(&amp;user); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
                return
            }
            // 处理业务...
            c.JSON(http.StatusCreated, user)
        })
    }

    r.Run(":8080")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">net/http</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/gin-gonic/gin</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> User struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ID   int    `json:&quot;id&quot;`</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Name string `json:&quot;name&quot;`</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    r </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 路由分组</span></span>
<span class="line"><span style="color: #D8DEE9FF">    v1 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">r</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Group</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/api/v1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">v1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">GET</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/ping</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            c.JSON(http.</span><span style="color: #D8DEE9">StatusOK</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> gin.H</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">: </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">pong</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">v1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">GET</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/users/:id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">id </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Param</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">JSON</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">StatusOK</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">User</span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">ID</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">张三</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">v1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">POST</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/users</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            var user </span><span style="color: #D8DEE9">User</span></span>
<span class="line"><span style="color: #D8DEE9FF">            if </span><span style="color: #88C0D0">err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ShouldBindJSON</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF">); </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                c.JSON(http.</span><span style="color: #D8DEE9">StatusBadRequest</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> gin.H</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">error</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 处理业务...</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">JSON</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">StatusCreated</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">r</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Run</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">:8080</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">4.2 自定义中间件</h3>



<p class="wp-block-paragraph">中间件是 Gin 的精华，可以用它来做日志、鉴权、限流等<a href="https://cloud.baidu.com/article/3694199" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "log"
    "time"
    "github.com/gin-gonic/gin"
)

// 日志中间件：记录请求耗时和状态码
func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path

        c.Next() // 执行后续处理器

        latency := time.Since(start)
        status := c.Writer.Status()

        log.Printf("&#91;%s&#93; %s %d %v", c.Request.Method, path, status, latency)
    }
}

// 鉴权中间件：简单 token 校验
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token != "Bearer secret-token" {
            c.JSON(401, gin.H{"error": "未授权"})
            c.Abort() // 中断请求链
            return
        }
        c.Next()
    }
}

func main() {
    r := gin.Default()

    // 全局中间件
    r.Use(LoggerMiddleware())

    // 需要鉴权的路由组
    authGroup := r.Group("/api")
    authGroup.Use(AuthMiddleware())
    {
        authGroup.GET("/profile", func(c *gin.Context) {
            c.JSON(200, gin.H{"user": "张三"})
        })
    }

    r.Run(":8080")
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/gin-gonic/gin</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 日志中间件：记录请求耗时和状态码</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">LoggerMiddleware</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">HandlerFunc</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">start </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> :</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Request</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">URL</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Path</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Next</span><span style="color: #D8DEE9FF">() </span><span style="color: #616E88">// 执行后续处理器</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">latency</span><span style="color: #D8DEE9FF"> :</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Since</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">start</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">status</span><span style="color: #D8DEE9FF"> :</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Writer</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Status</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">&#91;%s&#93; %s %d %v</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Request</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Method</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">status</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">latency</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 鉴权中间件：简单 token 校验</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AuthMiddleware</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">HandlerFunc</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">token </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">GetHeader</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Authorization</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">token</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Bearer secret-token</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            c.JSON(</span><span style="color: #B48EAD">401</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> gin.H</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">error</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">: </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">未授权</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            c.Abort() </span><span style="color: #616E88">// 中断请求链</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Next</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    r </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 全局中间件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">r</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Use</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">LoggerMiddleware</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 需要鉴权的路由组</span></span>
<span class="line"><span style="color: #D8DEE9FF">    authGroup </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">r</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Group</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/api</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">authGroup</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Use</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">AuthMiddleware</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">authGroup</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">GET</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/profile</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">gin</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            c.JSON(</span><span style="color: #B48EAD">200</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> gin.H</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">: </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">张三</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">r</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Run</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">:8080</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">中间件执行顺序遵循“先进后出”：最后注册的中间件最先执行前置逻辑，最先执行后置逻辑。理解这个机制可以帮助你写出更灵活的中间件组合。</p>



<h2 class="wp-block-heading">5. 常见坑点与最佳实践</h2>



<h3 class="wp-block-heading">5.1 nil 的判断陷阱</h3>



<p class="wp-block-paragraph">Go 中，接口类型的 nil 和具体类型的 nil 并不等价，这是最常见的坑之一<a href="https://developer.aliyun.com/article/1712638" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import "fmt"

type MyError struct{}

func (e *MyError) Error() string {
    return "my error"
}

func returnError() error {
    var err *MyError = nil
    return err // 返回的 error 接口不为 nil！
}

func main() {
    err := returnError()
    if err != nil {
        fmt.Printf("err 不为 nil: %v\n", err) // 这里会执行！
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> MyError struct{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">e</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">MyError</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">my error</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">returnError</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> *MyError </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 返回的 error 接口不为 nil！</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">returnError</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">err 不为 nil: %v</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 这里会执行！</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">解决方案：函数返回错误时，直接&nbsp;<code>return nil</code>，而不是返回一个值为 nil 的变量。</p>



<h3 class="wp-block-heading">5.2 循环变量捕获</h3>



<p class="wp-block-paragraph">在 for 循环中启动 goroutine 时，要注意闭包捕获的是同一个循环变量：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// ❌ 错误：所有 goroutine 都会打印最后一个值
for i := 0; i &lt; 5; i++ {
    go func() {
        fmt.Println(i)
    }()
}

// ✅ 正确：通过参数传递当前值
for i := 0; i &lt; 5; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// ❌ 错误：所有 goroutine 都会打印最后一个值</span></span>
<span class="line"><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ✅ 正确：通过参数传递当前值</span></span>
<span class="line"><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">n</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">n</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">5.3 依赖显式化，避免全局变量</h3>



<p class="wp-block-paragraph">日志、配置等依赖应该通过构造函数注入，而不是使用全局变量，这样代码更易于测试和维护<a href="https://developer.aliyun.com/article/1712638" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// ❌ 依赖隐式全局
var logger = log.New(os.Stdout, "", log.LstdFlags)

type Service struct{}

func (s *Service) DoSomething() {
    logger.Println("doing something") // 无法替换 logger
}

// ✅ 依赖显式注入
type Service struct {
    logger *log.Logger
}

func NewService(logger *log.Logger) *Service {
    return &amp;Service{logger: logger}
}

func (s *Service) DoSomething() {
    s.logger.Println("doing something") // 可控、可测
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// ❌ 依赖隐式全局</span></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">logger</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Stdout</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">LstdFlags</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Service struct{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">s</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Service</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">DoSomething</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">logger</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">doing something</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 无法替换 logger</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ✅ 依赖显式注入</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Service struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">    logger *log.Logger</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewService</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">logger</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Logger</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Service</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">Service</span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">logger</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">logger</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">s</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Service</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">DoSomething</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">s</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">logger</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">doing something</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">) </span><span style="color: #616E88">// 可控、可测</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">6. 开发效率工具</h2>



<h3 class="wp-block-heading">6.1 必备命令</h3>



<p class="wp-block-paragraph">几个几乎每天都会用到的命令<a href="https://datasea.cn/go0326556223.html" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># 格式化代码（必须，团队协作的底线）
gofmt -w .

# 静态检查，发现潜在 bug
go vet ./...

# 竞态检测，并发测试必开
go test -race ./...

# 整理依赖
go mod tidy

# 查看逃逸分析
go build -gcflags="-m"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">格式化代码</span><span style="color: #D8DEE9FF">（</span><span style="color: #D8DEE9">必须</span><span style="color: #D8DEE9FF">，</span><span style="color: #D8DEE9">团队协作的底线</span><span style="color: #D8DEE9FF">）</span></span>
<span class="line"><span style="color: #D8DEE9">gofmt</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">w</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">静态检查</span><span style="color: #D8DEE9FF">，</span><span style="color: #D8DEE9">发现潜在</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">bug</span></span>
<span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">vet</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">.</span><span style="color: #81A1C1">/...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">竞态检测</span><span style="color: #D8DEE9FF">，</span><span style="color: #D8DEE9">并发测试必开</span></span>
<span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">test</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">race</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">.</span><span style="color: #81A1C1">/...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">整理依赖</span></span>
<span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mod</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tidy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">查看逃逸分析</span></span>
<span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">build</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">gcflags</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">-m</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<h3 class="wp-block-heading">6.2 用好 pprof 做性能分析</h3>



<p class="wp-block-paragraph">Go 自带的 pprof 是性能调优的利器<a href="https://www.yisu.com/ask/95460261.html" target="_blank" rel="noreferrer noopener"></a>：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
)

func main() {
    // 启动 pprof HTTP 服务
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // 你的业务代码...
    select {}
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">net/http</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">net/http/pprof</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 启动 pprof HTTP 服务</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">http</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ListenAndServe</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">localhost:6060</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 你的业务代码...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">运行后访问&nbsp;<code>http://localhost:6060/debug/pprof/</code>&nbsp;就能看到 CPU、内存、goroutine 等各项指标。</p>



<h2 class="wp-block-heading">总结</h2>



<p class="wp-block-paragraph">Go 语言的魅力在于简洁和务实。写 Go 多年，最大的感悟是：好的 Go 代码不是玩出花活，而是让读代码的人一眼能看懂、改起来不害怕。最后送大家一句话：<strong>Go 爱诚实的人，别藏着掖着，也别假装看不见错误。</strong></p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e5%bc%80%e5%8f%91%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%93%ef%bc%9a%e4%bb%8e%e5%85%a5%e9%97%a8%e5%88%b0%e5%86%99%e5%87%ba%e7%94%9f%e4%ba%a7%e7%ba%a7%e4%bb%a3%e7%a0%81/"> Go 开发经验总结：从入门到写出生产级代码</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/go-%e5%bc%80%e5%8f%91%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%93%ef%bc%9a%e4%bb%8e%e5%85%a5%e9%97%a8%e5%88%b0%e5%86%99%e5%87%ba%e7%94%9f%e4%ba%a7%e7%ba%a7%e4%bb%a3%e7%a0%81/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>微服务全套docker-compose.yaml</title>
		<link>https://www.atomic-cube.cn/%e5%be%ae%e6%9c%8d%e5%8a%a1%e5%85%a8%e5%a5%97docker-compose-yaml/</link>
					<comments>https://www.atomic-cube.cn/%e5%be%ae%e6%9c%8d%e5%8a%a1%e5%85%a8%e5%a5%97docker-compose-yaml/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Fri, 17 Apr 2026 12:11:58 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Gin]]></category>
		<category><![CDATA[go-zero]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<category><![CDATA[微服务]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1417</guid>

					<description><![CDATA[<p>go-zero微服务开发，本地微服务开发环境</p>
<p>The post <a href="https://www.atomic-cube.cn/%e5%be%ae%e6%9c%8d%e5%8a%a1%e5%85%a8%e5%a5%97docker-compose-yaml/">微服务全套docker-compose.yaml</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">这份&nbsp;docker-compose.yml&nbsp;定义了一套完整的&nbsp;<strong>go-zero 微服务开发环境</strong>，覆盖了服务发现、消息队列、数据存储、监控追踪、日志中心等核心基础设施。它让开发者无需在本地繁琐安装配置，即可快速启动一套微服务所需的所有中间件，专注于业务代码开发</p>



<p class="wp-block-paragraph">该项目存在的意义，旨在于本地docker快速搭建微服务环境</p>



<p class="wp-block-paragraph">github地址：<a href="https://github.com/EvansYe2/microservice-docker-compose-deploy">https://github.com/EvansYe2/microservice-docker-compose-deploy</a></p>



<h2 class="wp-block-heading" id="Lwi6-1776600254249"><strong>一、服务发现（etcd）</strong></h2>



<p class="wp-block-paragraph"><strong>容器</strong>：etcd</p>



<ul class="wp-block-list">
<li><strong>作用</strong>：etcd 是分布式键值存储，在 go-zero 中充当&nbsp;<strong>服务注册与发现中心</strong>。每个微服务启动时将自己注册到 etcd，其他服务通过 etcd 获取对方地址，实现自动路由。</li>



<li><strong>配置</strong>：单节点模式，对外暴露 2379 端口供客户端（go-zero 服务）访问。</li>



<li><strong>协作</strong>：go-zero 服务通过 etcd 客户端连接&nbsp;etcd:2379&nbsp;完成注册与发现。</li>
</ul>



<h2 class="wp-block-heading" id="JPJ9-1776600254259"><strong>二、消息队列</strong></h2>



<h3 class="wp-block-heading" id="NIkz-1776600254261"><strong>1. Kafka 生态（Kafka + Zookeeper + Kafka-UI）</strong></h3>



<ul class="wp-block-list">
<li><strong>Zookeeper</strong>：Kafka 依赖 ZooKeeper 管理集群元数据、Broker 选举等。</li>



<li><strong>Kafka</strong>：高吞吐量分布式消息队列，用于&nbsp;<strong>异步解耦、削峰填谷、事件驱动</strong>。go-zero 中的异步任务（如发邮件、订单状态变更）可通过 Kafka 生产消费。</li>



<li><strong>Kafka-UI</strong>：Web 管理界面，可查看 Topic、消息、消费者组状态，便于开发调试。</li>



<li><strong>协作</strong>：go-zero 服务作为生产者将消息投递到 Kafka，消费者从 Kafka 拉取处理；Kafka-UI 连接 Kafka 和 Zookeeper 提供可视化。</li>
</ul>



<h3 class="wp-block-heading" id="YM2G-1776600254271"><strong>2. Beanstalkd 生态（Beanstalkd + Beanstalkd-Console）</strong></h3>



<ul class="wp-block-list">
<li><strong>Beanstalkd</strong>：轻量级&nbsp;<strong>延迟队列</strong>，专门处理延时任务（如订单超时未支付自动取消）。</li>



<li><strong>Beanstalkd-Console</strong>：Web 管理界面，查看队列、任务统计。</li>



<li><strong>协作</strong>：go-zero 服务可将延时任务投递到 Beanstalkd，消费者在指定时间后取出执行。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="k80j-1776600254280"><strong>三、数据库与缓存</strong></h2>



<ul class="wp-block-list">
<li><strong>MySQL</strong>：关系型数据库，存储业务数据（用户、订单等）。</li>



<li><strong>Redis</strong>：高性能内存数据库，用作&nbsp;<strong>缓存、分布式锁、会话存储、计数器</strong>&nbsp;等。</li>



<li><strong>协作</strong>：go-zero 服务通过 GORM 或原生 SQL 操作 MySQL，通过 go-redis 操作 Redis。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="doWB-1776600254289"><strong>四、监控与可观测性</strong></h2>



<h3 class="wp-block-heading" id="rXBY-1776600254291"><strong>1. Prometheus + Grafana</strong></h3>



<ul class="wp-block-list">
<li><strong>Prometheus</strong>：时序数据库，从 go-zero 服务抓取指标（请求量、延迟、错误率），存储为时间序列数据。
<ul class="wp-block-list">
<li>配置文件&nbsp;prometheus.yml&nbsp;和&nbsp;targets.json&nbsp;定义了抓取规则和目标（需列出具体服务端点）。</li>
</ul>
</li>



<li><strong>Grafana</strong>：可视化面板，连接 Prometheus 展示图表、仪表盘，便于分析系统状态。</li>



<li><strong>协作</strong>：go-zero 服务暴露&nbsp;/metrics&nbsp;端点，Prometheus 定期拉取；Grafana 读取 Prometheus 数据并展示。</li>
</ul>



<h3 class="wp-block-heading" id="MU7d-1776600254302"><strong>2. Jaeger（链路追踪）</strong></h3>



<ul class="wp-block-list">
<li><strong>Jaeger</strong>：分布式链路追踪系统，跟踪一个请求在多个微服务间的完整调用链，帮助定位性能瓶颈和故障点。</li>



<li><strong>存储</strong>：将链路数据写入 Elasticsearch（通过&nbsp;SPAN_STORAGE_TYPE=elasticsearch&nbsp;配置）。</li>



<li><strong>协作</strong>：go-zero 服务通过 OpenTelemetry 或 Jaeger 客户端将 trace 数据发送到 Jaeger 收集器（端口 14250/14268），Jaeger UI（端口 16686）提供查询界面。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="4zLE-1776600254311"><strong>五、日志中心（ELK + Filebeat + go-stash）</strong></h2>



<p class="wp-block-paragraph">日志流完整路径：</p>



<p class="wp-block-paragraph"><strong>Docker 容器日志 → Filebeat → Kafka → go-stash → Elasticsearch → Kibana</strong></p>



<h3 class="wp-block-heading" id="YxNV-1776600254317"><strong>1. Filebeat（日志采集）</strong></h3>



<ul class="wp-block-list">
<li>读取宿主机 Docker 容器日志（/var/lib/docker/containers/*/*.log），将日志发送到 Kafka 的&nbsp;go-zero-logs&nbsp;Topic。</li>



<li>配置文件&nbsp;filebeat.yml&nbsp;定义了输出目标（Kafka 地址、Topic 等）。</li>
</ul>



<h3 class="wp-block-heading" id="K6aY-1776600254323"><strong>2. Kafka（日志缓冲）</strong></h3>



<ul class="wp-block-list">
<li>作为日志中转站，削峰填谷，确保日志不会因突发流量丢失。</li>
</ul>



<h3 class="wp-block-heading" id="Ab2x-1776600254327"><strong>3. go-stash（日志处理与写入）</strong></h3>



<ul class="wp-block-list">
<li>从 Kafka 消费日志，按照配置规则处理后写入 Elasticsearch。</li>



<li>配置&nbsp;config.yaml&nbsp;中指定了 Kafka 和 Elasticsearch 地址、索引格式（如&nbsp;go-zero-logs-2006.01.02）。</li>
</ul>



<h3 class="wp-block-heading" id="f1qX-1776600254333"><strong>4. Elasticsearch（日志存储）</strong></h3>



<ul class="wp-block-list">
<li>分布式搜索引擎，存储日志数据，支持快速检索。</li>
</ul>



<h3 class="wp-block-heading" id="GyVO-1776600254337"><strong>5. Kibana（日志可视化）</strong></h3>



<ul class="wp-block-list">
<li>Elasticsearch 的 Web 界面，用于查询、分析、可视化日志。</li>
</ul>



<h3 class="wp-block-heading" id="8fNT-1776600254341"><strong>协作总结</strong></h3>



<ul class="wp-block-list">
<li>所有微服务的日志（通过 stdout/stderr）被 Docker 捕获，Filebeat 采集后发往 Kafka；go-stash 消费并写入 ES；开发者通过 Kibana 统一查看、搜索日志。</li>
</ul>



<h2 class="wp-block-heading" id="80ji-1776600254345"><strong>六、整体协作关系图（数据流）</strong></h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>阶段</td><td>组件</td><td>说明</td></tr><tr><td>服务启动</td><td>go-zero 服务 → etcd</td><td>注册自己</td></tr><tr><td>业务调用</td><td>go-zero 服务 → MySQL / Redis</td><td>数据读写</td></tr><tr><td>异步任务</td><td>go-zero 服务 → Kafka / Beanstalkd</td><td>发送消息或延时任务</td></tr><tr><td>监控</td><td>Prometheus → go-zero 服务</td><td>拉取&nbsp;/metrics</td></tr><tr><td>链路追踪</td><td>go-zero 服务 → Jaeger</td><td>发送 trace</td></tr><tr><td>日志</td><td>Filebeat → Kafka → go-stash → Elasticsearch → Kibana</td><td>采集、传输、存储、展示</td></tr></tbody></table></figure>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>version: '3.8'

networks:
  go-zero-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

volumes:
  etcd-data:
  mysql-data:
  redis-data:
  kafka-data:
  prometheus-data:
  grafana-data:
  jaeger-data:
  elasticsearch-data:
  go-stash:

services:
  # ==================== Service Discovery ====================
  etcd:
    image: quay.io/coreos/etcd:v3.5.10
    container_name: etcd
    restart: unless-stopped
    environment:
      - ETCD_NAME=etcd0
      - ETCD_DATA_DIR=/etcd-data
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380
      - ETCD_INITIAL_CLUSTER=etcd0=http://etcd:2380
      - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
      - ETCD_INITIAL_CLUSTER_STATE=new
    ports:
      - "2379:2379"
      - "2380:2380"
    volumes:
      - ./etcd-data:/etcd-data
    networks:
      - go-zero-network

  # ==================== Message Queue ====================
  #Kafka 依赖 ZooKeeper 来管理集群元数据、选举控制器等。它本身不参与业务消息，但却是 Kafka 正常工作的基础。
  zookeeper:
    image: zookeeper:3.9.5
    container_name: zookeeper
    restart: unless-stopped
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
      ZOO_MY_ID: 1
    ports:
      - "2181:2181"
    volumes:
      - ./zk-data:/var/lib/zookeeper/data
      - ./zk-datalog:/var/lib/zookeeper/log
    networks:
      - go-zero-network

  #Kafka 是一个高吞吐量分布式消息队列，用于 异步解耦、削峰填谷、事件驱动。go-zero 中的异步任务（如发送通知、订单处理）可以通过 Kafka 生产消费。
  kafka:
    image: apache/kafka:4.0.2
    container_name: kafka
    restart: unless-stopped
    depends_on:
      - zookeeper
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      # 外部访问监听端口为 9092，容器内部使用不同的端口 9094
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9094,CONTROLLER://localhost:9093,PLAINTEXT_CONTAINER://kafka:9092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9094,PLAINTEXT_CONTAINER://kafka:9092
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_CONTAINER:PLAINTEXT
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_NUM_PARTITIONS: 3
    ports:
      - "9092:9092"  # 容器内部之间使用的监听端口
      - "9094:9094"  # 容器外部访问监听端口
    volumes:
      - ./kafka-data:/var/lib/kafka/data
    networks:
      - go-zero-network

  #Kafka 的可视化 Web 管理界面，用于查看主题、消息、消费者组状态，方便开发和调试。
  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui
    restart: unless-stopped
    depends_on:
      - kafka
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
      KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
    ports:
      - "8080:8080"
    networks:
      - go-zero-network

  #Beanstalkd 是一个轻量级的 延迟队列，专门用于处理延时任务（如订单超时未支付自动取消）。
  beanstalkd:
    image: schickling/beanstalkd:latest
    container_name: beanstalkd
    restart: unless-stopped
    command: -l 0.0.0.0
    ports:
      - "11300:11300"
    networks:
      - go-zero-network

  #Beanstalkd 的 Web 管理界面，查看队列状态、任务统计等。
  beanstalkd-console:
    image: agaveapi/beanstalkd-console:latest
    container_name: beanstalkd-console
    restart: unless-stopped
    depends_on:
      - beanstalkd
    environment:
      BEANSTALKD_HOST: beanstalkd
      BEANSTALKD_PORT: 11300
    ports:
      - "8081:8080"
    networks:
      - go-zero-network

  # ==================== Database &amp; Cache ====================
  mysql:
    image: mysql:8.0
    container_name: mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: go_zero
      MYSQL_USER: go_zero
      MYSQL_PASSWORD: go_zero123
    ports:
      - "3306:3306"
    volumes:
      - ./mysql-data:/var/lib/mysql
    networks:
      - go-zero-network

  redis:
    image: redis:7.2-alpine
    container_name: redis
    restart: unless-stopped
    command: redis-server --requirepass 123456
    ports:
      - "6379:6379"
    volumes:
      - ./redis-data:/data
    networks:
      - go-zero-network

  

  # ==================== Monitoring &amp; Observability 监控业务====================
  #时序数据库，从 go-zero 服务抓取指标（请求量、延迟、错误率等），并存储为时间序列数据。
  #挂载了 prometheus.yml（抓取规则）和 targets.json（服务发现目标）。
  #targets.json 中应列出需要监控的服务端点（如 user-api:9081）。
  prometheus:
    image: prom/prometheus:v2.48.0
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./prometheus/targets.json:/etc/prometheus/targets.json:ro
      - ./prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.enable-lifecycle'
    ports:
      - "9090:9090"
    networks:
      - go-zero-network

  #prometheus 的 ui 很难看，用来显示 prometheus 收集来的数据，查看prometheus监控数据
  #可视化面板，从 Prometheus 读取数据并展示为图表、仪表盘。
  grafana:
    image: grafana/grafana:10.2.0
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_INSTALL_PLUGINS=grafana-piechart-panel
    volumes:
      - ./grafana-data:/var/lib/grafana
      - ./grafana/datasources:/etc/grafana/provisioning/datasources:ro
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
    ports:
      - "3000:3000"
    networks:
      - go-zero-network

  #jaeger链路追踪 — Jaeger for tracing
  #分布式链路追踪系统，用于跟踪一个请求在多个微服务之间的完整调用链，定位性能瓶颈。
  #将链路数据存储在 Elasticsearch 中（SPAN_STORAGE_TYPE=elasticsearch）。
  jaeger:
    image: jaegertracing/all-in-one:1.53
    container_name: jaeger
    restart: unless-stopped
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
    ports:
      - "5775:5775/udp"
      - "6831:6831/udp"
      - "6832:6832/udp"
      - "5778:5778"
      - "16686:16686"
      - "14268:14268"
      - "14250:14250"
      - "9411:9411"
    networks:
      - go-zero-network

  # ==================== Logging Stack ====================
  #存储收集的日志，分布式搜索引擎，用于存储和索引日志数据，支持快速检索。
  #Docker 容器日志 → Filebeat → Kafka → (go-stash/Logstash) → Elasticsearch → Kibana
  elasticsearch:
    image: elasticsearch:7.17.15
    container_name: elasticsearch
    restart: unless-stopped
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      - ./elasticsearch-data:/usr/share/elasticsearch/data
    networks:
      - go-zero-network

  #显示 elasticsearch日志，Elasticsearch 的可视化界面，用于查询、分析日志。
  kibana:
    image: kibana:7.17.15
    container_name: kibana
    restart: unless-stopped
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    ports:
      - "5601:5601"
    networks:
      - go-zero-network

  #消费kafka中filebeat收集的数据输出到es - The data output collected by FileBeat in Kafka is output to ES
  go-stash:
    image: kevinwan/go-stash:1.1.1
    container_name: go-stash
    environment:
      # 时区上海 - Time zone Shanghai (Change if needed)
      TZ: Asia/Shanghai
    restart: always
    volumes:
      - ./go-stash/etc:/app/etc
    networks:
      - go-zero-network
    depends_on:
      - elasticsearch
      - kafka


  #收集业务数据，收集日志到 kafka
  #轻量级日志采集器，读取 Docker 容器的日志（/var/lib/docker/containers/*/*.log），并将日志发送到 Kafka。
  #挂载了 filebeat.yml 定义输出目标（Kafka topic go-zero-logs）。
  #Docker 容器日志 → Filebeat → Kafka → (go-stash/Logstash) → Elasticsearch → Kibana
  filebeat:
    image: docker.elastic.co/beats/filebeat:7.17.15
    container_name: filebeat
    restart: unless-stopped
    user: root
    volumes:
      - ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - go-zero-network</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">version</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">3.8</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">go-zero-network</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">driver</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">bridge</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ipam</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">config</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">subnet</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">172.20.0.0/16</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">etcd-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">mysql-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">redis-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">kafka-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">prometheus-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">grafana-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">jaeger-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">elasticsearch-data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">go-stash</span><span style="color: #ECEFF4">:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">services</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88"># ==================== Service Discovery ====================</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">etcd</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">quay.io/coreos/etcd:v3.5.10</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">etcd</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_NAME=etcd0</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_DATA_DIR=/etcd-data</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_INITIAL_CLUSTER=etcd0=http://etcd:2380</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ETCD_INITIAL_CLUSTER_STATE=new</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2379:2379</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2380:2380</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./etcd-data:/etcd-data</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88"># ==================== Message Queue ====================</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Kafka 依赖 ZooKeeper 来管理集群元数据、选举控制器等。它本身不参与业务消息，但却是 Kafka 正常工作的基础。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">zookeeper</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">zookeeper:3.9.5</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">zookeeper</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">ZOOKEEPER_CLIENT_PORT</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2181</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">ZOOKEEPER_TICK_TIME</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2000</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">ZOO_MY_ID</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2181:2181</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./zk-data:/var/lib/zookeeper/data</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./zk-datalog:/var/lib/zookeeper/log</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Kafka 是一个高吞吐量分布式消息队列，用于 异步解耦、削峰填谷、事件驱动。go-zero 中的异步任务（如发送通知、订单处理）可以通过 Kafka 生产消费。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">kafka</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apache/kafka:4.0.2</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">depends_on</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">zookeeper</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_NODE_ID</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_PROCESS_ROLES</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">broker,controller</span></span>
<span class="line"><span style="color: #ECEFF4">      </span><span style="color: #616E88"># 外部访问监听端口为 9092，容器内部使用不同的端口 9094</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_LISTENERS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">PLAINTEXT://0.0.0.0:9094,CONTROLLER://localhost:9093,PLAINTEXT_CONTAINER://kafka:9092</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_ADVERTISED_LISTENERS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">PLAINTEXT://localhost:9094,PLAINTEXT_CONTAINER://kafka:9092</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_ZOOKEEPER_CONNECT</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">zookeeper:2181</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_CONTROLLER_LISTENER_NAMES</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">CONTROLLER</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_CONTAINER:PLAINTEXT</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_CONTROLLER_QUORUM_VOTERS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">1@localhost:9093</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_TRANSACTION_STATE_LOG_MIN_ISR</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_NUM_PARTITIONS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9092:9092</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">  </span><span style="color: #616E88"># 容器内部之间使用的监听端口</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9094:9094</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">  </span><span style="color: #616E88"># 容器外部访问监听端口</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./kafka-data:/var/lib/kafka/data</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Kafka 的可视化 Web 管理界面，用于查看主题、消息、消费者组状态，方便开发和调试。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">kafka-ui</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">provectuslabs/kafka-ui:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka-ui</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">depends_on</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_CLUSTERS_0_NAME</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">local</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka:9092</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">KAFKA_CLUSTERS_0_ZOOKEEPER</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">zookeeper:2181</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">8080:8080</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Beanstalkd 是一个轻量级的 延迟队列，专门用于处理延时任务（如订单超时未支付自动取消）。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">beanstalkd</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">schickling/beanstalkd:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">beanstalkd</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">command</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-l 0.0.0.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">11300:11300</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Beanstalkd 的 Web 管理界面，查看队列状态、任务统计等。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">beanstalkd-console</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">agaveapi/beanstalkd-console:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">beanstalkd-console</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">depends_on</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">beanstalkd</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">BEANSTALKD_HOST</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">beanstalkd</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">BEANSTALKD_PORT</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">11300</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">8081:8080</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88"># ==================== Database &amp; Cache ====================</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">mysql</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql:8.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_ROOT_PASSWORD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">123456</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_DATABASE</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go_zero</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_USER</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go_zero</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_PASSWORD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go_zero123</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">3306:3306</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./mysql-data:/var/lib/mysql</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">redis</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">redis:7.2-alpine</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">redis</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">command</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">redis-server --requirepass 123456</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">6379:6379</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./redis-data:/data</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88"># ==================== Monitoring &amp; Observability 监控业务====================</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#时序数据库，从 go-zero 服务抓取指标（请求量、延迟、错误率等），并存储为时间序列数据。</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#挂载了 prometheus.yml（抓取规则）和 targets.json（服务发现目标）。</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#targets.json 中应列出需要监控的服务端点（如 user-api:9081）。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">prometheus</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">prom/prometheus:v2.48.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">prometheus</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./prometheus/targets.json:/etc/prometheus/targets.json:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./prometheus-data:/prometheus</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">command</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">--config.file=/etc/prometheus/prometheus.yml</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">--storage.tsdb.path=/prometheus</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">--web.enable-lifecycle</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9090:9090</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#prometheus 的 ui 很难看，用来显示 prometheus 收集来的数据，查看prometheus监控数据</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#可视化面板，从 Prometheus 读取数据并展示为图表、仪表盘。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">grafana</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">grafana/grafana:10.2.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">grafana</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">GF_SECURITY_ADMIN_PASSWORD=admin</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">GF_INSTALL_PLUGINS=grafana-piechart-panel</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./grafana-data:/var/lib/grafana</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./grafana/datasources:/etc/grafana/provisioning/datasources:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">3000:3000</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#jaeger链路追踪 — Jaeger for tracing</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#分布式链路追踪系统，用于跟踪一个请求在多个微服务之间的完整调用链，定位性能瓶颈。</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#将链路数据存储在 Elasticsearch 中（SPAN_STORAGE_TYPE=elasticsearch）。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">jaeger</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">jaegertracing/all-in-one:1.53</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">jaeger</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">COLLECTOR_OTLP_ENABLED=true</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">SPAN_STORAGE_TYPE=elasticsearch</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ES_SERVER_URLS=http://elasticsearch:9200</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5775:5775/udp</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">6831:6831/udp</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">6832:6832/udp</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5778:5778</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">16686:16686</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">14268:14268</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">14250:14250</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9411:9411</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88"># ==================== Logging Stack ====================</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#存储收集的日志，分布式搜索引擎，用于存储和索引日志数据，支持快速检索。</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Docker 容器日志 → Filebeat → Kafka → (go-stash/Logstash) → Elasticsearch → Kibana</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">elasticsearch</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">elasticsearch:7.17.15</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">elasticsearch</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">discovery.type=single-node</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ES_JAVA_OPTS=-Xms512m -Xmx512m</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">xpack.security.enabled=false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9200:9200</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9300:9300</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./elasticsearch-data:/usr/share/elasticsearch/data</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#显示 elasticsearch日志，Elasticsearch 的可视化界面，用于查询、分析日志。</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">kibana</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kibana:7.17.15</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kibana</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">ELASTICSEARCH_HOSTS</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">http://elasticsearch:9200</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5601:5601</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#消费kafka中filebeat收集的数据输出到es - The data output collected by FileBeat in Kafka is output to ES</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">go-stash</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kevinwan/go-stash:1.1.1</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-stash</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #ECEFF4">      </span><span style="color: #616E88"># 时区上海 - Time zone Shanghai (Change if needed)</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">TZ</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Asia/Shanghai</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">always</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./go-stash/etc:/app/etc</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">depends_on</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">elasticsearch</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#收集业务数据，收集日志到 kafka</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#轻量级日志采集器，读取 Docker 容器的日志（/var/lib/docker/containers/*/*.log），并将日志发送到 Kafka。</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#挂载了 filebeat.yml 定义输出目标（Kafka topic go-zero-logs）。</span></span>
<span class="line"><span style="color: #ECEFF4">  </span><span style="color: #616E88">#Docker 容器日志 → Filebeat → Kafka → (go-stash/Logstash) → Elasticsearch → Kibana</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">filebeat</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker.elastic.co/beats/filebeat:7.17.15</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">filebeat</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">user</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">root</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/var/lib/docker/containers:/var/lib/docker/containers:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/var/run/docker.sock:/var/run/docker.sock:ro</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">networks</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-network</span></span></code></pre></div>



<p class="wp-block-paragraph">Prometheus 配置文件 (prometheus/prometheus.yml)</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'file_ds'
    file_sd_configs:
      - files:
          - /etc/prometheus/targets.json
        refresh_interval: 30s</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">global</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">scrape_interval</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">15s</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">evaluation_interval</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">15s</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">scrape_configs</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">job_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">file_ds</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">file_sd_configs</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">files</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/etc/prometheus/targets.json</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">refresh_interval</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">30s</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Prometheus 服务发现目标 (prometheus/targets.json)</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>[
  {
    "targets": &#91;"user-api:9081"&#93;,
    "labels": {
      "job": "user-api",
      "app": "user-api",
      "env": "test"
    }
  },
  {
    "targets": &#91;"user-rpc:9091"&#93;,
    "labels": {
      "job": "user-rpc",
      "app": "user-rpc",
      "env": "test"
    }
  },
  {
    "targets": &#91;"order-api:9082"&#93;,
    "labels": {
      "job": "order-api",
      "app": "order-api",
      "env": "test"
    }
  },
  {
    "targets": &#91;"order-rpc:9092"&#93;,
    "labels": {
      "job": "order-rpc",
      "app": "order-rpc",
      "env": "test"
    }
  }
]</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">targets</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user-api:9081</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">&#93;,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">labels</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user-api</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user-api</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">env</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">targets</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user-rpc:9091</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">&#93;,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">labels</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user-rpc</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">user-rpc</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">env</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">targets</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">order-api:9082</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">&#93;,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">labels</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">order-api</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">order-api</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">env</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">targets</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">order-rpc:9092</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">&#93;,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">labels</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">order-rpc</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">app</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">order-rpc</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">env</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Grafana 数据源配置 (grafana/datasources/prometheus.yaml)</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">apiVersion</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #8FBCBB">datasources</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Prometheus</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">type</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">prometheus</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">access</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">proxy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">url</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">http://prometheus:9090</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">isDefault</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Filebeat 配置文件 (filebeat/filebeat.yml)</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>filebeat.inputs:
  - type: container
    paths:
      - /var/lib/docker/containers/*/*.log

output.kafka:
  hosts: &#91;"kafka:9092"&#93;
  topic: "go-zero-logs"
  partition.round_robin:
    reachable_only: true

logging.level: info</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">filebeat.inputs</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">type</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">container</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">paths</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/var/lib/docker/containers/*/*.log</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">output.kafka</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">hosts</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">kafka:9092</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">topic</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">go-zero-logs</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">partition.round_robin</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">reachable_only</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">logging.level</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">info</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>go-stash/etc/config.yaml</strong><br></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Clusters:
  - Input:
      Kafka:
        Name: gostash
        Brokers:
          - kafka:9092
        Topics:
          - go-zero-logs
        Group: go-stash-group
        Consumer:
          Offset: first
    Output:
      ElasticSearch:
        Hosts:
          - http://elasticsearch:9200
        Index: go-zero-logs-{{yyyy-MM-dd}}
        # 可选：索引滚动策略等</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">Clusters</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Input</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">Kafka</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">gostash</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Brokers</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka:9092</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Topics</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-logs</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Group</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-stash-group</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Consumer</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #8FBCBB">Offset</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">first</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">Output</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">ElasticSearch</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Hosts</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">http://elasticsearch:9200</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Index</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-logs-{{yyyy-MM-dd}}</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88"># 可选：索引滚动策略等</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Go-zero 服务配置示例 (config.yaml)</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># 服务发现
Etcd:
  Hosts:
    - etcd:2379
  Key: user.rpc

# 链路追踪
Telemetry:
  Name: user-api
  Endpoint: http://jaeger:14268/api/traces
  Sampler: 1.0
  Batcher: jaeger

# Prometheus 监控
Prometheus:
  Host: 0.0.0.0
  Port: 9081
  Path: /metrics

# MySQL
DB:
  DataSource: root:123456@tcp(mysql:3306)/go_zero?charset=utf8mb4&amp;parseTime=true&amp;loc=Asia%2FShanghai

# Redis
Redis:
  Host: redis:6379
  Pass: 123456
  Type: node

# Kafka 配置
Kafka:
  Brokers:
    - kafka:9092
  GroupID: go-zero-group
  Topic: order-topic

# Beanstalkd 配置
Beanstalkd:
  Endpoint: beanstalkd:11300
  Tube: delay-tube</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># 服务发现</span></span>
<span class="line"><span style="color: #8FBCBB">Etcd</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Hosts</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">etcd:2379</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">user.rpc</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># 链路追踪</span></span>
<span class="line"><span style="color: #8FBCBB">Telemetry</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">user-api</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Endpoint</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">http://jaeger:14268/api/traces</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Sampler</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Batcher</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">jaeger</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Prometheus 监控</span></span>
<span class="line"><span style="color: #8FBCBB">Prometheus</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Host</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0.0.0.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Port</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">9081</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Path</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/metrics</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># MySQL</span></span>
<span class="line"><span style="color: #8FBCBB">DB</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">DataSource</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">root:123456@tcp(mysql:3306)/go_zero?charset=utf8mb4&amp;parseTime=true&amp;loc=Asia%2FShanghai</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Redis</span></span>
<span class="line"><span style="color: #8FBCBB">Redis</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Host</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">redis:6379</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Pass</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">123456</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Type</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">node</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Kafka 配置</span></span>
<span class="line"><span style="color: #8FBCBB">Kafka</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Brokers</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">kafka:9092</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">GroupID</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">go-zero-group</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Topic</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">order-topic</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Beanstalkd 配置</span></span>
<span class="line"><span style="color: #8FBCBB">Beanstalkd</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Endpoint</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">beanstalkd:11300</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">Tube</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">delay-tube</span></span></code></pre></div>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># 创建配置文件目录
mkdir -p prometheus grafana/datasources filebeat

# 将上述配置文件写入对应目录
# 启动所有容器
docker-compose up -d

验证服务状态
docker-compose ps

</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># 创建配置文件目录</span></span>
<span class="line"><span style="color: #A3BE8C">mkdir -p prometheus grafana/datasources filebeat</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># 将上述配置文件写入对应目录</span></span>
<span class="line"><span style="color: #616E88"># 启动所有容器</span></span>
<span class="line"><span style="color: #A3BE8C">docker-compose up -d</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">验证服务状态</span></span>
<span class="line"><span style="color: #A3BE8C">docker-compose ps</span></span>
<span class="line"></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>访问各服务 UI</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>服务</td><td>地址</td><td>说明</td></tr><tr><td>etcd</td><td><a href="http://localhost:2379/">http://localhost:2379</a></td><td>服务注册发现（etcd API）</td></tr><tr><td>Kafka UI</td><td><a href="http://localhost:8080/">http://localhost:8080</a></td><td>Kafka 管理界面</td></tr><tr><td>Beanstalkd Console</td><td><a href="http://localhost:8081/">http://localhost:8081</a></td><td>延迟队列管理界面</td></tr><tr><td>Redis Commander</td><td><a href="http://localhost:8082/">http://localhost:8082</a></td><td>Redis 管理界面</td></tr><tr><td>Prometheus</td><td><a href="http://localhost:9090/">http://localhost:9090</a></td><td>监控指标收集</td></tr><tr><td>Grafana</td><td><a href="http://localhost:3000/">http://localhost:3000</a></td><td>监控可视化（admin/admin）从 Prometheus 读取数据并展示为图表、仪表盘。</td></tr><tr><td>Jaeger UI</td><td><a href="http://localhost:16686/">http://localhost:16686</a></td><td>链路追踪查询</td></tr><tr><td>Kibana</td><td><a href="http://localhost:5601/">http://localhost:5601</a></td><td>日志分析,查看es日志</td></tr><tr><td>MySQL</td><td>localhost:3306</td><td>数据库</td></tr><tr><td>Redis</td><td>localhost:6379</td><td>缓存</td></tr><tr><td>etcd-keeper</td><td><a href="http://localhost:8080">http://localhost:8080</a></td><td></td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><a href="https://github.com/bitnami/containers/tree/main/bitnami/etcd#how-to-deploy-etcd-to-kubernetes"></a>访问 kibana <a href="https://xie.infoq.cn/link?target=http%3A%2F%2F127.0.0.1%3A5601%2F">http://127.0.0.1:5601/</a> ， 创建日志索引</p>



<p class="wp-block-paragraph">点击左上角菜单(三个横线那个东东)，找到 Analytics &#8211; &gt; 点击 discover</p>



<figure class="wp-block-image size-full"><a href="https://www.atomic-cube.cn/wp-content/uploads/2026/04/111.png"><img decoding="async" width="780" height="802" src="https://www.atomic-cube.cn/wp-content/uploads/2026/04/111.png" alt="" class="wp-image-1418" srcset="https://www.atomic-cube.cn/wp-content/uploads/2026/04/111.png 780w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/111-292x300.png 292w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/111-768x790.png 768w" sizes="(max-width: 780px) 100vw, 780px" /></a></figure>



<p class="wp-block-paragraph">然后在当前页面，Create index pattern-&gt;输入 go-zero-logs-* -&gt; Next Step -&gt;选择 @timestamp-&gt;Create index pattern</p>



<p class="wp-block-paragraph">然后点击左上角菜单，找到 Analytics-&gt;点击 discover ，日志都显示了 （如果不显示，就去排查 filebeat、go-stash，使用 docker logs -f filebeat 查看）<a href="https://github.com/bitnami/containers/tree/main/bitnami/etcd#how-to-deploy-etcd-to-kubernetes"></a></p>



<figure class="wp-block-image size-large"><a href="https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-scaled.png"><img decoding="async" width="1024" height="458" src="https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-1024x458.png" alt="" class="wp-image-1419" srcset="https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-1024x458.png 1024w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-300x134.png 300w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-768x344.png 768w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-1536x688.png 1536w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/222-2048x917.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>
<p>The post <a href="https://www.atomic-cube.cn/%e5%be%ae%e6%9c%8d%e5%8a%a1%e5%85%a8%e5%a5%97docker-compose-yaml/">微服务全套docker-compose.yaml</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/%e5%be%ae%e6%9c%8d%e5%8a%a1%e5%85%a8%e5%a5%97docker-compose-yaml/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>使用Go语言实现Redis分布式锁（附有看门狗自动续期机制）</title>
		<link>https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/</link>
					<comments>https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Thu, 26 Mar 2026 12:52:00 +0000</pubDate>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1446</guid>

					<description><![CDATA[<p>Redis我们日常开发经常使用，而分布式锁的一个重要实现就是通过Redis完成，分布式锁要解决的核心问题是防止 [&#8230;]</p>
<p>The post <a href="https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/">使用Go语言实现Redis分布式锁（附有看门狗自动续期机制）</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Redis我们日常开发经常使用，而分布式锁的一个重要实现就是通过Redis完成，分布式锁要解决的核心问题是<strong>防止对某个资源进行重复或者过度请求</strong>，例如我们在分布式系统中创建订单之前，必须获取分布式锁才能创建订单，其要解决的主要问题有两个：</p>



<p class="wp-block-paragraph">1）如果用户重复点击提交订单按钮，可以通过分布式锁<strong>避免重复创建订单</strong></p>



<p class="wp-block-paragraph">2）商品库存有限，通过分布式锁能够<strong>解决超卖问题</strong></p>



<p class="wp-block-paragraph">Redis实现分布式锁</p>



<p class="wp-block-paragraph">由于Redis中<code>set nx</code>命令的原子性，只有在键值不存在的时候才能设置值，因此可以通过<code>set nx</code>实现分布式锁，但是它有弊端就是：</p>



<p class="wp-block-paragraph">如果业务还没有执行完，那么会导致业务没有执行完，锁就被释放了</p>



<p class="wp-block-paragraph">因此延伸出了基于Redis延伸的Redisson分布式锁框架，它实现的原理在于<strong>使用Redis单线程模型执行SET NX命令lua脚本</strong>确保获取锁操作原子性，同时它内置了更为丰富的<strong>看门狗机制</strong>，满足了在业务执行过程中，自动续期锁</p>



<h2 class="wp-block-heading" id="2vq7h">Go语言实现分布式锁</h2>



<p class="wp-block-paragraph">上面都是基于理论介绍，接下来就实现基于Redis的分布式锁</p>



<h3 class="wp-block-heading" id="5ts0b">明确原子性操作</h3>



<p class="wp-block-paragraph">在实现之前，我们要明确<strong>加锁、释放锁</strong>与<strong>续期锁</strong>的机制：</p>



<p class="wp-block-paragraph"><strong>加锁</strong>：设置键值与过期时间，通过<code>setnx px</code>实现，如果键值已经存在直接返回错误</p>



<p class="wp-block-paragraph"><strong>释放锁</strong>：我们一定要确保释放的锁是自己加的锁，因此要判断value值是否是之前设置的value值，只有判断正确才能够释放锁</p>



<p class="wp-block-paragraph"><strong>续期锁</strong>：与释放锁同理，只有当前锁是自己加的锁才续期</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package utils

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
)

// 预定义错误，便于调用方进行错误判断
var (
	// ErrLockNotHeld 释放锁时发现锁不属于当前实例
	ErrLockNotHeld = errors.New("lock not held")
	// ErrLockAcquireFailed 加锁失败（因已被他人持有或超时）
	ErrLockAcquireFailed = errors.New("failed to acquire lock")
	// ErrLockLost 锁在持有期间丢失（续期失败），此时业务应中断
	ErrLockLost = errors.New("lock lost during hold")
)

// Logger 可注入的日志接口，便于集成各类日志库
type Logger interface {
	Printf(format string, v ...interface{})
}

// defaultLogger 默认日志实现，输出到标准输出
type defaultLogger struct{}

func (l *defaultLogger) Printf(format string, v ...interface{}) {
	fmt.Printf(format+"\n", v...)
}

// RenewFailedCallback 续期失败时的回调函数，用于通知业务方锁已丢失
type RenewFailedCallback func(key string, err error)

// RetryStrategy 重试策略类型，用于自定义获取锁失败时的等待行为
type RetryStrategy func(attempt int) time.Duration

// Mutex 基于 Redis 的分布式锁，内置看门狗自动续期。
// 每个实例仅允许一次 Lock → Unlock 生命周期，不可复用。
type Mutex struct {
	client redis.UniversalClient // Redis 客户端，支持单机、哨兵、集群
	key    string                // Redis 中锁的键名
	value  string                // 锁持有者唯一标识（UUID）
	ttl    time.Duration         // 锁的过期时间

	// 状态保护
	mu     sync.Mutex
	locked bool   // 是否已成功加锁
	lost   bool   // 锁是否已丢失（续期失败导致），丢失后不应再尝试释放
	stopCh chan struct{} // 通知看门狗退出的通道
	wg     sync.WaitGroup

	// 看门狗启动保护
	watchdogOnce sync.Once // 确保看门狗只启动一次

	// 配置项
	retryStrategy     RetryStrategy      // 自定义重试策略
	minRenewInterval  time.Duration      // 最小续期间隔，防止高频续期
	renewTimeout      time.Duration      // 续期操作的超时时间
	logger            Logger             // 日志接口
	renewFailedCb     RenewFailedCallback // 续期失败回调
}

// Option 函数式配置选项
type Option func(*Mutex)

// WithRetryDelay 设置固定间隔的重试策略（简单场景）
func WithRetryDelay(d time.Duration) Option {
	return func(m *Mutex) {
		m.retryStrategy = func(attempt int) time.Duration {
			return d
		}
	}
}

// WithExponentialBackoff 设置指数退避重试策略
// 初始间隔为 initial，最大间隔为 max，每次重试间隔翻倍
func WithExponentialBackoff(initial, max time.Duration) Option {
	return func(m *Mutex) {
		m.retryStrategy = func(attempt int) time.Duration {
			d := initial
			for i := 0; i &lt; attempt; i++ {
				d *= 2
				if d > max {
					return max
				}
			}
			return d
		}
	}
}

// WithMinRenewInterval 设置看门狗最小续期间隔，避免高频续期（默认 200ms）
func WithMinRenewInterval(d time.Duration) Option {
	return func(m *Mutex) {
		m.minRenewInterval = d
	}
}

// WithRenewTimeout 设置单次续期操作的超时时间（默认 2s）
func WithRenewTimeout(d time.Duration) Option {
	return func(m *Mutex) {
		m.renewTimeout = d
	}
}

// WithLogger 注入自定义日志实现
func WithLogger(l Logger) Option {
	return func(m *Mutex) {
		m.logger = l
	}
}

// WithRenewFailedCallback 设置续期失败回调
func WithRenewFailedCallback(cb RenewFailedCallback) Option {
	return func(m *Mutex) {
		m.renewFailedCb = cb
	}
}

// NewMutex 创建一个新的分布式锁实例
// client: Redis 客户端
// key: 锁的键名，建议使用业务唯一标识，如 "order:lock:123"
// ttl: 锁的过期时间，建议设置为业务处理时间的 2~3 倍
func NewMutex(client redis.UniversalClient, key string, ttl time.Duration, opts ...Option) *Mutex {
	if ttl &lt;= 0 {
		ttl = 30 * time.Second
	}
	m := &amp;Mutex{
		client:           client,
		key:              key,
		value:            uuid.New().String(), // 生成唯一持有者标识
		ttl:              ttl,
		stopCh:           make(chan struct{}),
		retryStrategy:    func(attempt int) time.Duration { return 50 * time.Millisecond }, // 默认固定 50ms
		minRenewInterval: 200 * time.Millisecond,
		renewTimeout:     2 * time.Second,
		logger:           &amp;defaultLogger{},
	}
	for _, opt := range opts {
		opt(m)
	}
	return m
}

// Lock 阻塞式获取锁，直到成功或 ctx 超时/取消。
// 成功获取锁后自动启动看门狗进行续期。
func (m *Mutex) Lock(ctx context.Context) error {
	attempt := 0
	for {
		ok, err := m.tryAcquire(ctx)
		if err != nil {
			return err
		}
		if ok {
			// 加锁成功，启动看门狗
			m.startWatchdog()
			return nil
		}

		// 未获取到锁，计算等待时间
		delay := m.retryStrategy(attempt)
		attempt++

		select {
		case &lt;-ctx.Done():
			return fmt.Errorf("lock %s acquire timeout: %w", m.key, ctx.Err())
		case &lt;-time.After(delay):
			// 继续重试
		}
	}
}

// TryLock 非阻塞尝试获取一次锁，立即返回结果。
// 若成功获取，会自动启动看门狗。
func (m *Mutex) TryLock(ctx context.Context) (bool, error) {
	ok, err := m.tryAcquire(ctx)
	if err != nil {
		return false, err
	}
	if ok {
		m.startWatchdog()
		return true, nil
	}
	return false, nil
}

// Unlock 释放锁，并停止看门狗。
// 只有锁的当前持有者才能成功释放，否则返回 ErrLockNotHeld。
// 若锁在持有期间已丢失（续期失败），则返回 ErrLockLost。
func (m *Mutex) Unlock(ctx context.Context) error {
	// 先停止看门狗，确保不再有续期操作
	m.stopWatchdog()

	m.mu.Lock()
	defer m.mu.Unlock()

	if m.lost {
		return ErrLockLost
	}
	if !m.locked {
		return ErrLockNotHeld
	}

	err := m.releaseLock(ctx)
	if err == nil {
		m.locked = false
	}
	return err
}

// IsHeld 检查当前实例是否仍持有锁（通过 Redis 实时验证）
func (m *Mutex) IsHeld(ctx context.Context) (bool, error) {
	val, err := m.client.Get(ctx, m.key).Result()
	if err == redis.Nil {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return val == m.value, nil
}

// tryAcquire 执行一次加锁尝试，内部更新 locked 状态
func (m *Mutex) tryAcquire(ctx context.Context) (bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.locked {
		return false, fmt.Errorf("mutex already locked")
	}

	ok, err := m.client.SetNX(ctx, m.key, m.value, m.ttl).Result()
	if err != nil {
		return false, err
	}
	if ok {
		m.locked = true
		m.lost = false
		return true, nil
	}
	return false, nil
}

// releaseLock 执行 Redis 释放锁的 Lua 脚本，确保原子性
func (m *Mutex) releaseLock(ctx context.Context) error {
	script := `
		if redis.call("GET", KEYS&#91;1&#93;) == ARGV&#91;1&#93; then
			return redis.call("DEL", KEYS&#91;1&#93;)
		else
			return 0
		end
	`
	result, err := m.client.Eval(ctx, script, []string{m.key}, m.value).Result()
	if err != nil {
		return fmt.Errorf("redis eval error: %w", err)
	}
	if result.(int64) == 0 {
		return ErrLockNotHeld
	}
	return nil
}

// startWatchdog 启动后台续期协程（内部使用 Once 保证只启动一次）
func (m *Mutex) startWatchdog() {
	m.watchdogOnce.Do(func() {
		m.wg.Add(1)
		go m.watchdogLoop()
	})
}

// watchdogLoop 看门狗主循环，定期续期
func (m *Mutex) watchdogLoop() {
	defer m.wg.Done()

	// 计算续期间隔：TTL 的 1/3，且不小于最小间隔
	interval := m.ttl / 3
	if interval &lt; m.minRenewInterval {
		interval = m.minRenewInterval
	}

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case &lt;-ticker.C:
			if err := m.renew(); err != nil {
				// 续期失败，标记锁已丢失，并通知外部
				m.markLost()
				m.logger.Printf("watchdog renew failed for key %s: %v", m.key, err)
				if m.renewFailedCb != nil {
					m.renewFailedCb(m.key, err)
				}
				return
			}
		case &lt;-m.stopCh:
			// 收到停止信号，正常退出
			return
		}
	}
}

// renew 执行一次续期操作，使用 Lua 脚本验证持有者身份并延长 TTL
func (m *Mutex) renew() error {
	ctx, cancel := context.WithTimeout(context.Background(), m.renewTimeout)
	defer cancel()

	script := `
		if redis.call("GET", KEYS&#91;1&#93;) == ARGV&#91;1&#93; then
			return redis.call("EXPIRE", KEYS&#91;1&#93;, ARGV&#91;2&#93;)
		else
			return 0
		end
	`
	result, err := m.client.Eval(ctx, script, []string{m.key}, m.value, int64(m.ttl.Seconds())).Result()
	if err != nil {
		return err
	}
	if result.(int64) == 0 {
		return errors.New("lock not held or key missing")
	}
	return nil
}

// markLost 标记锁已丢失，由续期失败时调用
func (m *Mutex) markLost() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.lost = true
	m.locked = false // 丢失后即认为不再持有锁
}

// stopWatchdog 停止看门狗协程并等待其退出
func (m *Mutex) stopWatchdog() {
	// 关闭 stopCh 通知看门狗退出（使用 select 避免重复关闭）
	select {
	case &lt;-m.stopCh:
		// 已经关闭
	default:
		close(m.stopCh)
	}
	// 等待看门狗协程完全退出
	m.wg.Wait()
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">utils</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">errors</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/google/uuid</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/redis/go-redis/v9</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 预定义错误，便于调用方进行错误判断</span></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// ErrLockNotHeld 释放锁时发现锁不属于当前实例</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ErrLockNotHeld</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock not held</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// ErrLockAcquireFailed 加锁失败（因已被他人持有或超时）</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ErrLockAcquireFailed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">failed to acquire lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// ErrLockLost 锁在持有期间丢失（续期失败），此时业务应中断</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ErrLockLost</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock lost during hold</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Logger 可注入的日志接口，便于集成各类日志库</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Logger interface {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	Printf(format string, v ...interface{})</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// defaultLogger 默认日志实现，输出到标准输出</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> defaultLogger struct{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">l</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">defaultLogger</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">format</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">interface</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">format</span><span style="color: #81A1C1">+</span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">v</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// RenewFailedCallback 续期失败时的回调函数，用于通知业务方锁已丢失</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> RenewFailedCallback func(key string, err error)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// RetryStrategy 重试策略类型，用于自定义获取锁失败时的等待行为</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> RetryStrategy func(attempt int) time.Duration</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Mutex 基于 Redis 的分布式锁，内置看门狗自动续期。</span></span>
<span class="line"><span style="color: #616E88">// 每个实例仅允许一次 Lock → Unlock 生命周期，不可复用。</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Mutex struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	client redis.UniversalClient </span><span style="color: #616E88">// Redis 客户端，支持单机、哨兵、集群</span></span>
<span class="line"><span style="color: #D8DEE9FF">	key    string                </span><span style="color: #616E88">// Redis 中锁的键名</span></span>
<span class="line"><span style="color: #D8DEE9FF">	value  string                </span><span style="color: #616E88">// 锁持有者唯一标识（UUID）</span></span>
<span class="line"><span style="color: #D8DEE9FF">	ttl    time.Duration         </span><span style="color: #616E88">// 锁的过期时间</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 状态保护</span></span>
<span class="line"><span style="color: #D8DEE9FF">	mu     sync.Mutex</span></span>
<span class="line"><span style="color: #D8DEE9FF">	locked bool   </span><span style="color: #616E88">// 是否已成功加锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">	lost   bool   </span><span style="color: #616E88">// 锁是否已丢失（续期失败导致），丢失后不应再尝试释放</span></span>
<span class="line"><span style="color: #D8DEE9FF">	stopCh chan struct{} </span><span style="color: #616E88">// 通知看门狗退出的通道</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF">     </span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">WaitGroup</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 看门狗启动保护</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">watchdogOnce</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Once</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 确保看门狗只启动一次</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 配置项</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">retryStrategy</span><span style="color: #D8DEE9FF">     </span><span style="color: #D8DEE9">RetryStrategy</span><span style="color: #D8DEE9FF">      </span><span style="color: #616E88">// 自定义重试策略</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">minRenewInterval</span><span style="color: #D8DEE9FF">  </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">      </span><span style="color: #616E88">// 最小续期间隔，防止高频续期</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">renewTimeout</span><span style="color: #D8DEE9FF">      </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">      </span><span style="color: #616E88">// 续期操作的超时时间</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">logger</span><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">Logger</span><span style="color: #D8DEE9FF">             </span><span style="color: #616E88">// 日志接口</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">renewFailedCb</span><span style="color: #D8DEE9FF">     </span><span style="color: #D8DEE9">RenewFailedCallback</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 续期失败回调</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Option 函数式配置选项</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Option func(*Mutex)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithRetryDelay 设置固定间隔的重试策略（简单场景）</span></span>
<span class="line"><span style="color: #D8DEE9FF">func WithRetryDelay(d time.Duration) Option {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">retryStrategy</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			return </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithExponentialBackoff 设置指数退避重试策略</span></span>
<span class="line"><span style="color: #616E88">// 初始间隔为 initial，最大间隔为 max，每次重试间隔翻倍</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithExponentialBackoff</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">initial</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">max</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">retryStrategy</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #88C0D0">d </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">initial</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> :</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">; </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF">; </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				d *= </span><span style="color: #B48EAD">2</span></span>
<span class="line"><span style="color: #88C0D0">				if d &gt; max {</span></span>
<span class="line"><span style="color: #88C0D0">					return max</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			return </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithMinRenewInterval 设置看门狗最小续期间隔，避免高频续期（默认 200ms）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithMinRenewInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">minRenewInterval</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithRenewTimeout 设置单次续期操作的超时时间（默认 2s）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithRenewTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">renewTimeout</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithLogger 注入自定义日志实现</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithLogger</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">l</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Logger</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">logger</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">l</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithRenewFailedCallback 设置续期失败回调</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithRenewFailedCallback</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">cb</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">RenewFailedCallback</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">renewFailedCb</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">cb</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// NewMutex 创建一个新的分布式锁实例</span></span>
<span class="line"><span style="color: #616E88">// client: Redis 客户端</span></span>
<span class="line"><span style="color: #616E88">// key: 锁的键名，建议使用业务唯一标识，如 &quot;order:lock:123&quot;</span></span>
<span class="line"><span style="color: #616E88">// ttl: 锁的过期时间，建议设置为业务处理时间的 2~3 倍</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">UniversalClient</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">opts</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">30</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	m </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">Mutex</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		client</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">           </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">              </span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		value</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">uuid</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">String</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 生成唯一持有者标识</span></span>
<span class="line"><span style="color: #D8DEE9FF">		ttl</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">              </span><span style="color: #D8DEE9">ttl</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		stopCh</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">           </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">struct</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		retryStrategy</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">50</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 默认固定 50ms</span></span>
<span class="line"><span style="color: #D8DEE9FF">		minRenewInterval</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">200</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		renewTimeout</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">     </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		logger</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">           </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">defaultLogger</span><span style="color: #ECEFF4">{},</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opt </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">opts</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">opt</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Lock 阻塞式获取锁，直到成功或 ctx 超时/取消。</span></span>
<span class="line"><span style="color: #616E88">// 成功获取锁后自动启动看门狗进行续期。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	attempt </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">tryAcquire</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">			</span><span style="color: #616E88">// 加锁成功，启动看门狗</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">startWatchdog</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// 未获取到锁，计算等待时间</span></span>
<span class="line"><span style="color: #D8DEE9FF">		delay </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">retryStrategy</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">attempt</span><span style="color: #81A1C1">++</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">():</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock %s acquire timeout: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Err</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">After</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">delay</span><span style="color: #D8DEE9FF">):</span></span>
<span class="line"><span style="color: #ECEFF4">			</span><span style="color: #616E88">// 继续重试</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// TryLock 非阻塞尝试获取一次锁，立即返回结果。</span></span>
<span class="line"><span style="color: #616E88">// 若成功获取，会自动启动看门狗。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">tryAcquire</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">startWatchdog</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Unlock 释放锁，并停止看门狗。</span></span>
<span class="line"><span style="color: #616E88">// 只有锁的当前持有者才能成功释放，否则返回 ErrLockNotHeld。</span></span>
<span class="line"><span style="color: #616E88">// 若锁在持有期间已丢失（续期失败），则返回 ErrLockLost。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 先停止看门狗，确保不再有续期操作</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">stopWatchdog</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">lost</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockLost</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockNotHeld</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">releaseLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// IsHeld 检查当前实例是否仍持有锁（通过 Redis 实时验证）</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">val</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// tryAcquire 执行一次加锁尝试，内部更新 locked 状态</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">tryAcquire</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">mutex already locked</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">SetNX</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">lost</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// releaseLock 执行 Redis 释放锁的 Lua 脚本，确保原子性</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">releaseLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	script </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #A3BE8C">		if redis.call(&quot;GET&quot;, KEYS&#91;1&#93;) == ARGV&#91;1&#93; then</span></span>
<span class="line"><span style="color: #A3BE8C">			return redis.call(&quot;DEL&quot;, KEYS&#91;1&#93;)</span></span>
<span class="line"><span style="color: #A3BE8C">		else</span></span>
<span class="line"><span style="color: #A3BE8C">			return 0</span></span>
<span class="line"><span style="color: #A3BE8C">		end</span></span>
<span class="line"><span style="color: #A3BE8C">	</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Eval</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">script</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">m.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">redis eval error: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">int64</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockNotHeld</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// startWatchdog 启动后台续期协程（内部使用 Once 保证只启动一次）</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">startWatchdog</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">watchdogOnce</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Do</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.wg.Add(</span><span style="color: #B48EAD">1</span><span style="color: #88C0D0">)</span></span>
<span class="line"><span style="color: #88C0D0">		go m.watchdogLoop</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// watchdogLoop 看门狗主循环，定期续期</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">watchdogLoop</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 计算续期间隔：TTL 的 1/3，且不小于最小间隔</span></span>
<span class="line"><span style="color: #D8DEE9FF">	interval </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">interval</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">minRenewInterval</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">interval</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">minRenewInterval</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	ticker </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewTicker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">interval</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ticker</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stop</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ticker</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">C</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">renew</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">				</span><span style="color: #616E88">// 续期失败，标记锁已丢失，并通知外部</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">markLost</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">logger</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">watchdog renew failed for key %s: %v</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">renewFailedCb</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">renewFailedCb</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">stopCh</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #ECEFF4">			</span><span style="color: #616E88">// 收到停止信号，正常退出</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// renew 执行一次续期操作，使用 Lua 脚本验证持有者身份并延长 TTL</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">renew</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">renewTimeout</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	script </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #A3BE8C">		if redis.call(&quot;GET&quot;, KEYS&#91;1&#93;) == ARGV&#91;1&#93; then</span></span>
<span class="line"><span style="color: #A3BE8C">			return redis.call(&quot;EXPIRE&quot;, KEYS&#91;1&#93;, ARGV&#91;2&#93;)</span></span>
<span class="line"><span style="color: #A3BE8C">		else</span></span>
<span class="line"><span style="color: #A3BE8C">			return 0</span></span>
<span class="line"><span style="color: #A3BE8C">		end</span></span>
<span class="line"><span style="color: #A3BE8C">	</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Eval</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">script</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">m.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">int64</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ttl</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Seconds</span><span style="color: #D8DEE9FF">()))</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">int64</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock not held or key missing</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// markLost 标记锁已丢失，由续期失败时调用</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">markLost</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">lost</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 丢失后即认为不再持有锁</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// stopWatchdog 停止看门狗协程并等待其退出</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">stopWatchdog</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 关闭 stopCh 通知看门狗退出（使用 select 避免重复关闭）</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">stopCh</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// 已经关闭</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">stopCh</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 等待看门狗协程完全退出</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">完整测试用例：</p>



<p class="wp-block-paragraph">测试依赖&nbsp;<code>miniredis</code>&nbsp;模拟 Redis 服务，请先安装：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>go get github.com/alicebob/miniredis/v2</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">get</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">github</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">com</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">alicebob</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">miniredis</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">v2</span></span></code></pre></div>



<p class="wp-block-paragraph">测试文件：<code>mutex_test.go</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package redislock

import (
    "context"
    "sync"
    "testing"
    "time"

    "github.com/alicebob/miniredis/v2"
    "github.com/redis/go-redis/v9"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

// 创建测试用的 Redis 客户端（基于 miniredis）
func setupTestRedis(t *testing.T) (*redis.Client, *miniredis.Miniredis) {
    mr, err := miniredis.Run()
    require.NoError(t, err)

    client := redis.NewClient(&amp;redis.Options{
        Addr: mr.Addr(),
    })
    return client, mr
}

// 测试：正常加锁和解锁流程
func TestMutex_LockUnlock(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex := NewMutex(client, "test:lock", 5*time.Second)
    ctx := context.Background()

    // 加锁
    err := mutex.Lock(ctx)
    require.NoError(t, err)

    // 验证 Redis 中确实存在该 key
    val, err := client.Get(ctx, "test:lock").Result()
    require.NoError(t, err)
    assert.Equal(t, mutex.value, val)

    // 解锁
    err = mutex.Unlock(ctx)
    require.NoError(t, err)

    // 验证 key 已被删除
    _, err = client.Get(ctx, "test:lock").Result()
    assert.ErrorIs(t, err, redis.Nil)
}

// 测试：TryLock 非阻塞获取
func TestMutex_TryLock(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex1 := NewMutex(client, "test:trylock", 5*time.Second)
    mutex2 := NewMutex(client, "test:trylock", 5*time.Second)

    ctx := context.Background()

    // 第一个实例获取锁应成功
    ok, err := mutex1.TryLock(ctx)
    require.NoError(t, err)
    assert.True(t, ok)

    // 第二个实例获取锁应失败（已被占用）
    ok, err = mutex2.TryLock(ctx)
    require.NoError(t, err)
    assert.False(t, ok)

    // 释放锁后，第二个实例应能获取
    err = mutex1.Unlock(ctx)
    require.NoError(t, err)

    ok, err = mutex2.TryLock(ctx)
    require.NoError(t, err)
    assert.True(t, ok)
}

// 测试：阻塞等待超时
func TestMutex_LockTimeout(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    // 先占用锁
    holder := NewMutex(client, "test:timeout", 10*time.Second)
    err := holder.TryLock(context.Background())
    require.NoError(t, err)
    defer holder.Unlock(context.Background())

    // 第二个实例尝试获取，设置较短超时
    waiter := NewMutex(client, "test:timeout", 10*time.Second)
    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()

    start := time.Now()
    err = waiter.Lock(ctx)
    elapsed := time.Since(start)

    assert.Error(t, err)
    assert.Contains(t, err.Error(), "acquire timeout")
    assert.True(t, elapsed >= 500*time.Millisecond)
    assert.True(t, elapsed &lt; 1*time.Second)
}

// 测试：看门狗自动续期
func TestMutex_WatchdogRenew(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    // 设置较短的 TTL 和续期间隔，加快测试
    mutex := NewMutex(
        client,
        "test:watchdog",
        2*time.Second,
        WithMinRenewInterval(200*time.Millisecond),
    )

    ctx := context.Background()
    err := mutex.Lock(ctx)
    require.NoError(t, err)

    // 快进 Redis 时间，模拟 3 秒过去（超过原始 TTL）
    mr.FastForward(3 * time.Second)

    // 锁应该仍然存在（被看门狗续期）
    val, err := client.Get(ctx, "test:watchdog").Result()
    require.NoError(t, err)
    assert.Equal(t, mutex.value, val)

    // 释放锁
    err = mutex.Unlock(ctx)
    require.NoError(t, err)
}

// 测试：续期失败回调
func TestMutex_RenewFailedCallback(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    callbackCalled := make(chan error, 1)
    mutex := NewMutex(
        client,
        "test:callback",
        2*time.Second,
        WithMinRenewInterval(100*time.Millisecond),
        WithRenewFailedCallback(func(key string, err error) {
            callbackCalled &lt;- err
        }),
    )

    ctx := context.Background()
    err := mutex.Lock(ctx)
    require.NoError(t, err)

    // 模拟锁被外部删除（如手动 DEL）
    client.Del(ctx, "test:callback")

    // 等待回调被触发（续期时会发现锁不存在）
    select {
    case err := &lt;-callbackCalled:
        assert.Error(t, err)
    case &lt;-time.After(2 * time.Second):
        t.Fatal("续期失败回调未被调用")
    }

    // 解锁时应返回 ErrLockLost
    err = mutex.Unlock(ctx)
    assert.ErrorIs(t, err, ErrLockLost)
}

// 测试：锁持有者身份校验（防止误解锁）
func TestMutex_IdentityProtection(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex1 := NewMutex(client, "test:identity", 5*time.Second)
    mutex2 := NewMutex(client, "test:identity", 5*time.Second)

    ctx := context.Background()

    // mutex1 加锁
    err := mutex1.Lock(ctx)
    require.NoError(t, err)

    // mutex2 尝试解锁应失败
    err = mutex2.Unlock(ctx)
    assert.ErrorIs(t, err, ErrLockNotHeld)

    // mutex1 解锁应成功
    err = mutex1.Unlock(ctx)
    assert.NoError(t, err)
}

// 测试：IsHeld 实时状态查询
func TestMutex_IsHeld(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex := NewMutex(client, "test:isheld", 5*time.Second)
    ctx := context.Background()

    // 加锁前
    held, err := mutex.IsHeld(ctx)
    require.NoError(t, err)
    assert.False(t, held)

    // 加锁后
    err = mutex.Lock(ctx)
    require.NoError(t, err)
    held, err = mutex.IsHeld(ctx)
    require.NoError(t, err)
    assert.True(t, held)

    // 解锁后
    err = mutex.Unlock(ctx)
    require.NoError(t, err)
    held, err = mutex.IsHeld(ctx)
    require.NoError(t, err)
    assert.False(t, held)
}

// 测试：并发竞争场景
func TestMutex_Concurrent(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    const goroutines = 20
    var wg sync.WaitGroup
    counter := 0
    var mu sync.Mutex // 仅用于保护 counter 的本地累加

    for i := 0; i &lt; goroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            mutex := NewMutex(client, "test:concurrent", 5*time.Second)
            ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
            defer cancel()

            if err := mutex.Lock(ctx); err != nil {
                t.Logf("goroutine 获取锁失败: %v", err)
                return
            }
            defer mutex.Unlock(context.Background())

            // 临界区：安全地修改共享资源
            mu.Lock()
            counter++
            mu.Unlock()

            // 模拟业务处理
            time.Sleep(50 * time.Millisecond)
        }()
    }

    wg.Wait()

    // 最终 counter 应等于成功获取锁的 goroutine 数量（即 goroutines）
    assert.Equal(t, goroutines, counter)
}

// 测试：指数退避重试策略
func TestMutex_ExponentialBackoff(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    // 先占用锁
    holder := NewMutex(client, "test:backoff", 10*time.Second)
    err := holder.TryLock(context.Background())
    require.NoError(t, err)
    defer holder.Unlock(context.Background())

    // 第二个实例使用指数退避
    waiter := NewMutex(
        client,
        "test:backoff",
        10*time.Second,
        WithExponentialBackoff(50*time.Millisecond, 1*time.Second),
    )

    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()

    start := time.Now()
    err = waiter.Lock(ctx)
    elapsed := time.Since(start)

    assert.Error(t, err)
    // 验证确实等待了一段时间（由于退避，总等待时间应大于最小间隔）
    assert.True(t, elapsed > 100*time.Millisecond)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redislock</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">testing</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/alicebob/miniredis/v2</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/redis/go-redis/v9</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/stretchr/testify/assert</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/stretchr/testify/require</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 创建测试用的 Redis 客户端（基于 miniredis）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) (</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">miniredis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Miniredis</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">miniredis</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Run</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    client </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewClient</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Options</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">Addr</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Addr</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：正常加锁和解锁流程</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_LockUnlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 加锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 验证 Redis 中确实存在该 key</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">val</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Equal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 解锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 验证 key 已被删除</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ErrorIs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Nil</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：TryLock 非阻塞获取</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex1 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:trylock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex2 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:trylock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第一个实例获取锁应成功</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第二个实例获取锁应失败（已被占用）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex2</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">False</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 释放锁后，第二个实例应能获取</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex2</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：阻塞等待超时</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_LockTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 先占用锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    holder </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:timeout</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第二个实例尝试获取，设置较短超时</span></span>
<span class="line"><span style="color: #D8DEE9FF">    waiter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:timeout</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    start </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">waiter</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    elapsed </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Since</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">start</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Contains</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">acquire timeout</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">elapsed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">elapsed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：看门狗自动续期</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_WatchdogRenew</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 设置较短的 TTL 和续期间隔，加快测试</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:watchdog</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">2</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithMinRenewInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">200</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 快进 Redis 时间，模拟 3 秒过去（超过原始 TTL）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">FastForward</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 锁应该仍然存在（被看门狗续期）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">val</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:watchdog</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Equal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 释放锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：续期失败回调</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_RenewFailedCallback</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    callbackCalled </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:callback</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">2</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithMinRenewInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithRenewFailedCallback</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            callbackCalled &lt;- </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 模拟锁被外部删除（如手动 DEL）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Del</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:callback</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 等待回调被触发（续期时会发现锁不存在）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF">callbackCalled</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">After</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">):</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">续期失败回调未被调用</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 解锁时应返回 ErrLockLost</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ErrorIs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockLost</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：锁持有者身份校验（防止误解锁）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_IdentityProtection</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex1 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:identity</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex2 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:identity</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// mutex1 加锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// mutex2 尝试解锁应失败</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex2</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ErrorIs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockNotHeld</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// mutex1 解锁应成功</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：IsHeld 实时状态查询</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:isheld</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 加锁前</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">held</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">False</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">held</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 加锁后</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">held</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">held</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 解锁后</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">held</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">False</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">held</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：并发竞争场景</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_Concurrent</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">goroutines</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">20</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> sync.WaitGroup</span></span>
<span class="line"><span style="color: #D8DEE9FF">    counter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mu</span><span style="color: #D8DEE9FF"> sync.Mutex </span><span style="color: #616E88">// 仅用于保护 counter 的本地累加</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">goroutines</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:concurrent</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Logf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">goroutine 获取锁失败: %v</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 临界区：安全地修改共享资源</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">counter</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 模拟业务处理</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">50</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 最终 counter 应等于成功获取锁的 goroutine 数量（即 goroutines）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Equal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">goroutines</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">counter</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：指数退避重试策略</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_ExponentialBackoff</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 先占用锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    holder </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:backoff</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第二个实例使用指数退避</span></span>
<span class="line"><span style="color: #D8DEE9FF">    waiter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:backoff</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithExponentialBackoff</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">50</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    start </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">waiter</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    elapsed </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Since</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">start</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 验证确实等待了一段时间（由于退避，总等待时间应大于最小间隔）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">elapsed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">运行测试</h2>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>go test -v -race ./...</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">test</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">race</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">.</span><span style="color: #81A1C1">/...</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>-race</code>&nbsp;标志会启用数据竞争检测，确保并发安全。</p>



<p class="wp-block-paragraph"><br>注意事项</p>



<ol start="1" class="wp-block-list">
<li><strong>miniredis 时间快进</strong>：<code>mr.FastForward()</code>&nbsp;会推进 Redis 内部时钟，用于模拟过期时间，这对测试看门狗续期非常有用。</li>



<li><strong>测试并发安全</strong>：使用&nbsp;<code>-race</code>&nbsp;运行测试可发现潜在的竞态条件。</li>



<li><strong>生产环境日志</strong>：示例中的&nbsp;<code>Logger</code>&nbsp;接口可以替换为&nbsp;<code>*slog.Logger</code>&nbsp;或&nbsp;<code>logrus</code>&nbsp;实现，方便集成。</li>



<li><strong>上下文传递</strong>：建议业务层在调用&nbsp;<code>Lock</code>&nbsp;时传入带有&nbsp;<code>trace_id</code>&nbsp;的 context，便于问题追踪。</li>
</ol>
<p>The post <a href="https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/">使用Go语言实现Redis分布式锁（附有看门狗自动续期机制）</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Go 文件和文件夹工具类：一个开箱即用的 fileutil 包</title>
		<link>https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/</link>
					<comments>https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 07:28:00 +0000</pubDate>
				<category><![CDATA[Gin]]></category>
		<category><![CDATA[go-zero]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1510</guid>

					<description><![CDATA[<p>在日常开发中，文件与目录操作是高频需求。虽然 Go 标准库已经提供了&#160;os、path/filepat [&#8230;]</p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/">Go 文件和文件夹工具类：一个开箱即用的 fileutil 包</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在日常开发中，文件与目录操作是高频需求。虽然 Go 标准库已经提供了&nbsp;<code>os</code>、<code>path/filepath</code>&nbsp;等基础能力，但直接使用仍需处理大量边界条件。本文将封装一个实用的&nbsp;<code>fileutil</code>&nbsp;工具包，涵盖常用操作：路径存在判断、目录创建、文件复制、目录复制、移动、删除、遍历、大小统计等。每个方法都有详细的注释和错误处理，可直接集成到项目中。</p>



<p class="wp-block-paragraph"><strong>完整代码可在文末获取，或直接复制使用。</strong></p>



<h2 class="wp-block-heading">功能概览</h2>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="128" src="https://www.atomic-cube.cn/wp-content/uploads/2026/04/file1-1024x128.png" alt="" class="wp-image-1511" srcset="https://www.atomic-cube.cn/wp-content/uploads/2026/04/file1-1024x128.png 1024w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/file1-300x38.png 300w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/file1-768x96.png 768w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/file1-1536x192.png 1536w, https://www.atomic-cube.cn/wp-content/uploads/2026/04/file1-2048x257.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">核心代码：fileutil 包实现<br></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// Package fileutil 提供文件和目录操作的常用工具函数
package fileutil

import (
    "fmt"
    "io"
    "os"
    "path/filepath"
    "strings"
)

// Exists 判断路径是否存在（文件或目录均适用）
func Exists(path string) bool {
    _, err := os.Stat(path)
    return err == nil || !os.IsNotExist(err)
}

// IsDir 判断给定路径是否为目录
func IsDir(path string) (bool, error) {
    info, err := os.Stat(path)
    if err != nil {
        return false, err
    }
    return info.IsDir(), nil
}

// IsFile 判断给定路径是否为普通文件
func IsFile(path string) (bool, error) {
    info, err := os.Stat(path)
    if err != nil {
        return false, err
    }
    return !info.IsDir(), nil
}

// EnsureDir 确保目录存在，如果不存在则递归创建。
// 若已存在但不是目录，返回错误。
func EnsureDir(path string) error {
    if Exists(path) {
        isDir, err := IsDir(path)
        if err != nil {
            return err
        }
        if !isDir {
            return fmt.Errorf("path exists but is not a directory: %s", path)
        }
        return nil
    }
    return os.MkdirAll(path, 0755)
}

// CopyFile 复制单个文件从 src 到 dst。
// 如果 dst 已存在，默认会被覆盖。
// 复制时会保留源文件的权限模式。
func CopyFile(src, dst string) error {
    // 打开源文件
    srcFile, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("open source file: %w", err)
    }
    defer srcFile.Close()

    // 获取源文件信息（用于保留权限）
    srcInfo, err := srcFile.Stat()
    if err != nil {
        return fmt.Errorf("stat source file: %w", err)
    }

    // 创建目标文件
    dstFile, err := os.Create(dst)
    if err != nil {
        return fmt.Errorf("create destination file: %w", err)
    }
    defer dstFile.Close()

    // 复制内容
    if _, err := io.Copy(dstFile, srcFile); err != nil {
        return fmt.Errorf("copy content: %w", err)
    }

    // 同步写入到磁盘
    if err := dstFile.Sync(); err != nil {
        return fmt.Errorf("sync destination file: %w", err)
    }

    // 保留源文件的权限模式
    return os.Chmod(dst, srcInfo.Mode())
}

// MoveFile 移动文件（也可用于重命名）。
// 如果跨文件系统移动失败，会自动降级为复制后删除。
func MoveFile(src, dst string) error {
    // 先尝试直接 rename（同一文件系统内最快）
    err := os.Rename(src, dst)
    if err == nil {
        return nil
    }

    // 跨文件系统时，采用复制+删除
    if err := CopyFile(src, dst); err != nil {
        return fmt.Errorf("copy file during move: %w", err)
    }
    if err := os.Remove(src); err != nil {
        // 复制成功但删除失败，目标文件已存在，记录错误但不回滚（避免数据丢失）
        return fmt.Errorf("remove source file after copy: %w", err)
    }
    return nil
}

// CopyDir 递归复制整个目录从 src 到 dst。
// 如果 dst 已存在且是目录，内容会合并；否则会先创建。
func CopyDir(src, dst string) error {
    // 获取源目录信息
    srcInfo, err := os.Stat(src)
    if err != nil {
        return fmt.Errorf("stat source directory: %w", err)
    }
    if !srcInfo.IsDir() {
        return fmt.Errorf("source is not a directory: %s", src)
    }

    // 创建目标目录（保留源目录权限）
    if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
        return fmt.Errorf("create destination directory: %w", err)
    }

    // 遍历源目录
    entries, err := os.ReadDir(src)
    if err != nil {
        return fmt.Errorf("read source directory: %w", err)
    }

    for _, entry := range entries {
        srcPath := filepath.Join(src, entry.Name())
        dstPath := filepath.Join(dst, entry.Name())

        if entry.IsDir() {
            // 递归复制子目录
            if err := CopyDir(srcPath, dstPath); err != nil {
                return err
            }
        } else {
            // 复制文件
            if err := CopyFile(srcPath, dstPath); err != nil {
                return err
            }
        }
    }
    return nil
}

// DeleteDir 删除目录及其所有内容（类似 rm -rf）
func DeleteDir(path string) error {
    if !Exists(path) {
        return nil // 幂等：已不存在即成功
    }
    return os.RemoveAll(path)
}

// ListFiles 递归列出目录下所有文件的绝对路径。
// 如果指定了 extensions（例如 []string{".go", ".txt"}），则只返回匹配的文件。
// 传 nil 或空切片则返回所有文件。
func ListFiles(dir string, extensions []string) ([]string, error) {
    var files []string
    err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
        if err != nil {
            return err
        }
        if d.IsDir() {
            return nil
        }

        // 过滤扩展名
        if len(extensions) > 0 {
            ext := strings.ToLower(filepath.Ext(path))
            matched := false
            for _, e := range extensions {
                if strings.ToLower(e) == ext {
                    matched = true
                    break
                }
            }
            if !matched {
                return nil
            }
        }

        absPath, err := filepath.Abs(path)
        if err != nil {
            return err
        }
        files = append(files, absPath)
        return nil
    })
    return files, err
}

// DirSize 计算目录占用磁盘总大小（递归包含子目录）
func DirSize(path string) (int64, error) {
    var size int64
    err := filepath.WalkDir(path, func(_ string, d os.DirEntry, err error) error {
        if err != nil {
            return err
        }
        if !d.IsDir() {
            info, err := d.Info()
            if err != nil {
                return err
            }
            size += info.Size()
        }
        return nil
    })
    return size, err
}

// ReadString 快速读取整个文件为字符串
func ReadString(path string) (string, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

// WriteString 快速将字符串写入文件（覆盖写入，权限 0644）
func WriteString(path, content string) error {
    return os.WriteFile(path, []byte(content), 0644)
}

// AppendString 追加字符串到文件末尾，如果文件不存在则创建
func AppendString(path, content string) error {
    f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer f.Close()
    _, err = f.WriteString(content)
    return err
}

// SafeWrite 原子性写入：先写临时文件，再 rename 替换，避免写入中途崩溃导致文件损坏。
func SafeWrite(path string, data []byte, perm os.FileMode) error {
    // 在同目录下创建临时文件
    tmpFile, err := os.CreateTemp(filepath.Dir(path), ".tmp-*")
    if err != nil {
        return err
    }
    tmpName := tmpFile.Name()

    // 写入数据
    if _, err := tmpFile.Write(data); err != nil {
        tmpFile.Close()
        os.Remove(tmpName)
        return err
    }
    if err := tmpFile.Sync(); err != nil {
        tmpFile.Close()
        os.Remove(tmpName)
        return err
    }
    if err := tmpFile.Close(); err != nil {
        os.Remove(tmpName)
        return err
    }

    // 设置权限
    if err := os.Chmod(tmpName, perm); err != nil {
        os.Remove(tmpName)
        return err
    }

    // 原子替换
    return os.Rename(tmpName, path)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// Package fileutil 提供文件和目录操作的常用工具函数</span></span>
<span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">io</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">os</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path/filepath</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">strings</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Exists 判断路径是否存在（文件或目录均适用）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsNotExist</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// IsDir 判断给定路径是否为目录</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// IsFile 判断给定路径是否为普通文件</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">IsFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// EnsureDir 确保目录存在，如果不存在则递归创建。</span></span>
<span class="line"><span style="color: #616E88">// 若已存在但不是目录，返回错误。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">EnsureDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">isDir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">isDir</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path exists but is not a directory: %s</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0755</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// CopyFile 复制单个文件从 src 到 dst。</span></span>
<span class="line"><span style="color: #616E88">// 如果 dst 已存在，默认会被覆盖。</span></span>
<span class="line"><span style="color: #616E88">// 复制时会保留源文件的权限模式。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 打开源文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Open</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">open source file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 获取源文件信息（用于保留权限）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">stat source file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 创建目标文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Create</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">create destination file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 复制内容</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">io</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Copy</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcFile</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">copy content: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 同步写入到磁盘</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sync</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync destination file: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 保留源文件的权限模式</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Chmod</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Mode</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MoveFile 移动文件（也可用于重命名）。</span></span>
<span class="line"><span style="color: #616E88">// 如果跨文件系统移动失败，会自动降级为复制后删除。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">MoveFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 先尝试直接 rename（同一文件系统内最快）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Rename</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 跨文件系统时，采用复制+删除</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">copy file during move: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 复制成功但删除失败，目标文件已存在，记录错误但不回滚（避免数据丢失）</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">remove source file after copy: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// CopyDir 递归复制整个目录从 src 到 dst。</span></span>
<span class="line"><span style="color: #616E88">// 如果 dst 已存在且是目录，内容会合并；否则会先创建。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dst</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 获取源目录信息</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stat</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">stat source directory: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">source is not a directory: %s</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 创建目标目录（保留源目录权限）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MkdirAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">srcInfo</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Mode</span><span style="color: #D8DEE9FF">())</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">create destination directory: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 遍历源目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">entries</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">read source directory: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> entry </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entries</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        srcPath </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Join</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">src</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        dstPath </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Join</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dst</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">entry</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsDir</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 递归复制子目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">srcPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstPath</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 复制文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">srcPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dstPath</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// DeleteDir 删除目录及其所有内容（类似 rm -rf）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DeleteDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 幂等：已不存在即成功</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">RemoveAll</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ListFiles 递归列出目录下所有文件的绝对路径。</span></span>
<span class="line"><span style="color: #616E88">// 如果指定了 extensions（例如 []string{&quot;.go&quot;, &quot;.txt&quot;}），则只返回匹配的文件。</span></span>
<span class="line"><span style="color: #616E88">// 传 nil 或空切片则返回所有文件。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ListFiles</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dir</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">extensions</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) ([]</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">files</span><span style="color: #D8DEE9FF"> []string</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WalkDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">dir</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">DirEntry</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if err != nil </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if d.IsDir() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 过滤扩展名</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if </span><span style="color: #88C0D0">len</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">extensions</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> &gt; 0 </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            ext </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">strings</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ToLower</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Ext</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            matched </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> e </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">extensions</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">strings</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ToLower</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">e</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ext</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #D8DEE9">matched</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">break</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">matched</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">absPath</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Abs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            return </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">files</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">append</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">files</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">absPath</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">files</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// DirSize 计算目录占用磁盘总大小（递归包含子目录）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DirSize</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">int64</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF"> int64</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WalkDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">DirEntry</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if err != nil </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if !d.IsDir() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Info</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">info</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Size</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        return </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">size</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// ReadString 快速读取整个文件为字符串</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ReadString</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ReadFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">string</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WriteString 快速将字符串写入文件（覆盖写入，权限 0644）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// AppendString 追加字符串到文件末尾，如果文件不存在则创建</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AppendString</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">f</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">OpenFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_APPEND</span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_CREATE</span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">O_WRONLY</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">f</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">f</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">content</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// SafeWrite 原子性写入：先写临时文件，再 rename 替换，避免写入中途崩溃导致文件损坏。</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">SafeWrite</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">byte</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">FileMode</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 在同目录下创建临时文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">CreateTemp</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">filepath</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Dir</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.tmp-*</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    tmpName </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Name</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 写入数据</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Write</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sync</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">tmpFile</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 设置权限</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Chmod</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">perm</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Remove</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 原子替换</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">os</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Rename</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">tmpName</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">path</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">使用示例</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package main

import (
    "fmt"
    "log"
    "path/to/fileutil" // 替换为你的实际导入路径
)

func main() {
    // 1. 判断路径是否存在
    fmt.Println("存在:", fileutil.Exists("/tmp/test"))

    // 2. 确保目录存在
    if err := fileutil.EnsureDir("./data/logs"); err != nil {
        log.Fatal(err)
    }

    // 3. 写入文件
    if err := fileutil.WriteString("./data/config.txt", "Hello, Gopher!"); err != nil {
        log.Fatal(err)
    }

    // 4. 安全写入（原子操作）
    if err := fileutil.SafeWrite("./data/important.dat", []byte("atomic write"), 0644); err != nil {
        log.Fatal(err)
    }

    // 5. 复制文件
    if err := fileutil.CopyFile("./data/config.txt", "./data/backup/config_copy.txt"); err != nil {
        log.Fatal(err)
    }

    // 6. 复制目录
    if err := fileutil.CopyDir("./data", "./data_backup"); err != nil {
        log.Fatal(err)
    }

    // 7. 列出所有 .go 文件
    goFiles, err := fileutil.ListFiles(".", []string{".go"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("找到 %d 个 Go 文件\n", len(goFiles))

    // 8. 计算目录大小
    size, err := fileutil.DirSize("./data")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("目录大小: %.2f MB\n", float64(size)/1024/1024)

    // 9. 移动文件
    if err := fileutil.MoveFile("./data/old.txt", "./data/archive/new.txt"); err != nil {
        log.Fatal(err)
    }

    // 10. 删除目录
    if err := fileutil.DeleteDir("./data_backup"); err != nil {
        log.Fatal(err)
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path/to/fileutil</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 替换为你的实际导入路径</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">main</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 1. 判断路径是否存在</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Println</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">存在:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Exists</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/tmp/test</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 2. 确保目录存在</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">EnsureDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/logs</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 3. 写入文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WriteString</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/config.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Hello, Gopher!</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 4. 安全写入（原子操作）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">SafeWrite</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/important.dat</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #88C0D0">byte</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">atomic write</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0644</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 5. 复制文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">CopyFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/config.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/backup/config_copy.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 6. 复制目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">CopyDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data_backup</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 7. 列出所有 .go 文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">goFiles</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ListFiles</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.go</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">找到 %d 个 Go 文件</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">len</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">goFiles</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 8. 计算目录大小</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">size</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">DirSize</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">目录大小: %.2f MB</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">float64</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">size</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">1024</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">1024</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 9. 移动文件</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">MoveFile</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/old.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data/archive/new.txt</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 10. 删除目录</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fileutil</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">DeleteDir</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./data_backup</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">log</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">要点说明</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">函数</th><th class="has-text-align-left" data-align="left">注意事项</th></tr></thead><tbody><tr><td><code>CopyFile</code></td><td>覆盖已存在的目标文件；保留源文件权限</td></tr><tr><td><code>MoveFile</code></td><td>跨文件系统自动降级为复制+删除</td></tr><tr><td><code>CopyDir</code></td><td>递归复制，目标目录存在时会合并内容</td></tr><tr><td><code>SafeWrite</code></td><td>原子写入，先写临时文件再 rename，防止写一半时进程崩溃导致数据损坏</td></tr><tr><td><code>ListFiles</code></td><td>支持按扩展名过滤，返回绝对路径</td></tr><tr><td><code>EnsureDir</code></td><td>已存在但不是目录时报错，避免误用</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">这个工具包覆盖了日常开发中 90% 的文件目录操作场景。你可以根据项目需要继续扩展，比如增加压缩/解压、权限批量修改等功能。直接拷贝到项目中使用即可。</p>
<p>The post <a href="https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/">Go 文件和文件夹工具类：一个开箱即用的 fileutil 包</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/go-%e6%96%87%e4%bb%b6%e5%92%8c%e6%96%87%e4%bb%b6%e5%a4%b9%e5%b7%a5%e5%85%b7%e7%b1%bb%ef%bc%9a%e4%b8%80%e4%b8%aa%e5%bc%80%e7%ae%b1%e5%8d%b3%e7%94%a8%e7%9a%84-fileutil-%e5%8c%85/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
