<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Charles</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://dev.net.cn/images/apple-touch-icon-next.png</icon>
  <id>https://dev.net.cn/</id>
  <link href="https://dev.net.cn/" rel="alternate"/>
  <link href="https://dev.net.cn/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Charles</rights>
  <subtitle>Mind And Hand</subtitle>
  <title>码农笔记</title>
  <updated>2026-06-02T02:00:00.000Z</updated>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <category term="GoWeb" scheme="https://dev.net.cn/tags/GoWeb/"/>
    <category term="Gin" scheme="https://dev.net.cn/tags/Gin/"/>
    <content>
      <![CDATA[<p>今天学习：</p><ul><li>ShouldBindJSON</li><li>binding tag</li><li>参数校验</li><li>统一错误响应</li><li>业务错误和参数错误区分</li></ul><h2 id="ShouldBindJSON"><a href="#ShouldBindJSON" class="headerlink" title="ShouldBindJSON"></a>ShouldBindJSON</h2><p><code>ShouldBindJSON</code>会把请求body里的<code>JSON</code>绑定到<code>struct</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> CreateUserRequest <span class="keyword">struct</span> &#123;</span><br><span class="line">Name  <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">Email <span class="type">string</span> <span class="string">`json:&quot;email&quot;`</span></span><br><span class="line">Age   <span class="type">int</span>    <span class="string">`json:&quot;age&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createUser</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> req CreateUserRequest</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;req); err != <span class="literal">nil</span> &#123;</span><br><span class="line">c.JSON(http.StatusBadRequest, gin.H&#123;</span><br><span class="line"><span class="string">&quot;error&quot;</span>: err.Error(),</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>:    <span class="number">1</span>,</span><br><span class="line"><span class="string">&quot;name&quot;</span>:  req.Name,</span><br><span class="line"><span class="string">&quot;email&quot;</span>: req.Email,</span><br><span class="line"><span class="string">&quot;age&quot;</span>:   req.Age,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：<code>c.ShouldBindJSON(&amp;req)</code>这里必须传递指针，因为要修改req的值。</p><p>除了<code>ShouldBindJSON</code>，还有一个<code>BindJSON</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">err := c.BindJSON(&amp;req)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果报错，会直接返回。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">400 Bad Request</span><br></pre></td></tr></table></figure><p>不过出于灵活性，工作中常用的是<code>ShouldBindJSON</code>。</p><h2 id="validator-binding-tag"><a href="#validator-binding-tag" class="headerlink" title="validator:binding tag"></a>validator:binding tag</h2><p>对于熟悉<code>Spring</code>的Java程序员来说，它就相当于<code>@Validated</code>、<code>@Valid</code>注解，以及各种字段注解：<code>@NotNull</code>、<code>@Size</code>、<code>@Min</code>等。</p><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> CreateUserRequest <span class="keyword">struct</span> &#123;</span><br><span class="line">Name     <span class="type">string</span> <span class="string">`json:&quot;name&quot; binding:&quot;required,min=2,max=5&quot;`</span></span><br><span class="line">Email    <span class="type">string</span> <span class="string">`json:&quot;email&quot; binding:&quot;required,email&quot;`</span></span><br><span class="line">Password <span class="type">string</span> <span class="string">`json:&quot;password&quot; binding:&quot;required,min=8&quot;`</span></span><br><span class="line">Age      <span class="type">int</span>    <span class="string">`json:&quot;age&quot; binding:&quot;required,gte=18,lte=120&quot;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在常规<code>json struct</code>中，加入<code>binding</code>，常见规则有：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">required : 必填</span><br><span class="line">email    : 必须是邮箱格式</span><br><span class="line">min      : 最小长度或者最小值</span><br><span class="line">max      : 最大长度或者最大值</span><br><span class="line">gte      : 大于等于</span><br><span class="line">lte      : 小于等于</span><br><span class="line">oneof    : 枚举值</span><br></pre></td></tr></table></figure><p>关于<code>oneof</code>，以角色状态为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Status   <span class="type">string</span>  <span class="string">`json:&quot;status&quot; binding:&quot;required,oneof=active disabled&quot;`</span></span><br></pre></td></tr></table></figure><p>当然，这些知识基础校验，通常也是放在<code>Request DTO</code>的<code>binding tag</code>里，对于业务校验，还应该放在<code>service</code>层。例如用户已存在、邮箱后缀不符等。</p><h2 id="统一错误响应"><a href="#统一错误响应" class="headerlink" title="统一错误响应"></a>统一错误响应</h2><p>不要每个<code>handler</code>随便返回不同格式，对于前端是个灾难，对于Java来说，通常都会定一个一<code>Response</code>类，返回固定格式:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;msg&quot;</span><span class="punctuation">:</span><span class="string">&quot;xxxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span><span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">    “data”<span class="punctuation">:</span>object</span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>对于Go Web当然也都是一样的操作（俗称java味？）。</p><p>创建一个response包，并创建一个<code>response.go</code>文件，内容如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> response</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ErrorResponse <span class="keyword">struct</span> &#123;</span><br><span class="line">Code    <span class="type">string</span> <span class="string">`json:&quot;code&quot;`</span></span><br><span class="line">Message <span class="type">string</span> <span class="string">`json:&quot;message&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Error</span><span class="params">(c *gin.Context, status <span class="type">int</span>, code, message <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">c.JSON(status, ErrorResponse&#123;</span><br><span class="line">Code:    code,</span><br><span class="line">Message: message,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BadRequest</span><span class="params">(c *gin.Context, message <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">Error(c, http.StatusBadRequest, <span class="string">&quot;BAD_REQUEST&quot;</span>, message)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Unauthorized</span><span class="params">(c *gin.Context, message <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">Error(c, http.StatusUnauthorized, <span class="string">&quot;UNAUTHORIZED&quot;</span>, message)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">OK</span><span class="params">(c *gin.Context, data any)</span></span> &#123;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;data&quot;</span>: data,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用办法，直接在原来的例子中修改为如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line"><span class="comment">// 导入</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/response&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createUser</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> req CreateUserRequest</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;req); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">        c.JSON(http.StatusBadRequest, gin.H&#123;</span></span><br><span class="line"><span class="comment">&quot;error&quot;: err.Error(),</span></span><br><span class="line"><span class="comment">&#125;)</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">        <span class="comment">// 修改为如下</span></span><br><span class="line">response.BadRequest(c, err.Error())</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">response.OK(c, gin.H&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>:    <span class="number">1</span>,</span><br><span class="line"><span class="string">&quot;name&quot;</span>:  req.Name,</span><br><span class="line"><span class="string">&quot;email&quot;</span>: req.Email,</span><br><span class="line"><span class="string">&quot;age&quot;</span>:   req.Age,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">POST http://localhost:8080/user</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;name&quot;</span>:<span class="string">&quot;张三&quot;</span>,</span><br><span class="line">    <span class="string">&quot;email&quot;</span>:<span class="string">&quot;zhangsan@dev.net&quot;</span>,</span><br><span class="line">    <span class="string">&quot;password&quot;</span>:<span class="string">&quot;123456&quot;</span>,</span><br><span class="line">    <span class="string">&quot;age&quot;</span>:28</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>响应如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;BAD_REQUEST&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Key: &#x27;CreateUserRequest.Password&#x27; Error:Field validation for &#x27;Password&#x27; failed on the &#x27;min&#x27; tag&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>将<code>password</code>的值修改为8位，响应如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">28</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zhangsan@dev.net&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;张三&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="业务错误类型"><a href="#业务错误类型" class="headerlink" title="业务错误类型"></a>业务错误类型</h2><p>除了常规错误意外，系统中还应处理业务错误，例如：邮箱已存在、用户名已存在等。</p><p>创建<code>errs</code>包，并创建<code>error.go</code>文件，内容为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> errs</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> AppError <span class="keyword">struct</span> &#123;</span><br><span class="line">Code       <span class="type">string</span></span><br><span class="line">Message    <span class="type">string</span></span><br><span class="line">HTTPStatus <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *AppError)</span></span> Error() <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> e.Code + <span class="string">&quot; : &quot;</span> + e.Message</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ErrUserNotFound = &amp;AppError&#123;</span><br><span class="line">Code:       <span class="string">&quot;USER_NOT_FOUND&quot;</span>,</span><br><span class="line">Message:    <span class="string">&quot;User not found&quot;</span>,</span><br><span class="line">HTTPStatus: <span class="number">404</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ErrEmailExists = &amp;AppError&#123;</span><br><span class="line">Code:       <span class="string">&quot;EMAIL_EXISTS&quot;</span>,</span><br><span class="line">Message:    <span class="string">&quot;Email exists&quot;</span>,</span><br><span class="line">HTTPStatus: <span class="number">409</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>response</code>包里面的<code>resopnse.go</code>文件中，新增如下内容：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 别忘了 import &quot;dev.net.cn/goweb/errs&quot;</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">HandleError</span><span class="params">(c *gin.Context, err <span class="type">error</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> appErr, ok := errors.AsType[*errs.AppError](err); ok &#123;</span><br><span class="line">c.JSON(appErr.HTTPStatus, gin.H&#123;</span><br><span class="line"><span class="string">&quot;code&quot;</span>:    appErr.Code,</span><br><span class="line"><span class="string">&quot;message&quot;</span>: appErr.Message,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.JSON(http.StatusInternalServerError, gin.H&#123;</span><br><span class="line"><span class="string">&quot;code&quot;</span>:    <span class="string">&quot;INTERNAL_ERROR&quot;</span>,</span><br><span class="line"><span class="string">&quot;message&quot;</span>: <span class="string">&quot;internal server error&quot;</span>,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改<code>main.go</code>，新增如下代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">validateEmail</span><span class="params">(email <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errs.ErrEmailExists</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改原来的<code>createUser</code>代码为如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createUser</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> req CreateUserRequest</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;req); err != <span class="literal">nil</span> &#123;</span><br><span class="line">response.BadRequest(c, err.Error())</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := validateEmail(req.Email); err != <span class="literal">nil</span> &#123;</span><br><span class="line">response.HandleError(c, err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">response.OK(c, gin.H&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>:    <span class="number">1</span>,</span><br><span class="line"><span class="string">&quot;name&quot;</span>:  req.Name,</span><br><span class="line"><span class="string">&quot;email&quot;</span>: req.Email,</span><br><span class="line"><span class="string">&quot;age&quot;</span>:   req.Age,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用这个接口：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;code&quot;</span>: <span class="string">&quot;EMAIL_EXISTS&quot;</span>,</span><br><span class="line">    <span class="string">&quot;message&quot;</span>: <span class="string">&quot;Email exists&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-go-web-02/</id>
    <link href="https://dev.net.cn/learning-go-web-02/"/>
    <published>2026-06-02T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天学习：</p>
<ul>
<li>ShouldBindJSON</li>
<li>binding tag</li>
<li>参数校验</li>
<li>统一错误响应</li>
<li>业务错误和参数错误区分</li>
</ul>
<h2 id="ShouldBindJS]]>
    </summary>
    <title>Go Web学习笔记（二）- JSON binding、validator、错误响应</title>
    <updated>2026-06-02T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <category term="GoWeb" scheme="https://dev.net.cn/tags/GoWeb/"/>
    <category term="Gin" scheme="https://dev.net.cn/tags/Gin/"/>
    <content>
      <![CDATA[<p>今天开始学习Go Web，目前主流的框架有<code>gin</code>、<code>go-zero</code>、<code>echo</code>等。我还是随主流，选择<code>gin</code>。</p><p>今天要学习的知识点是：</p><ul><li>启动Gin服务</li><li>定义<code>Get</code> &#x2F; <code>POST</code> &#x2F; <code>PUT</code> &#x2F; <code>DELETE</code> &#x2F; <code>路由</code></li><li>获取路径参数</li><li>获取<code>query</code>参数</li><li>返回<code>JSON</code>响应</li><li>路由分组</li></ul><h2 id="创建一个gin项目"><a href="#创建一个gin项目" class="headerlink" title="创建一个gin项目"></a>创建一个gin项目</h2><h3 id="初始化项目"><a href="#初始化项目" class="headerlink" title="初始化项目"></a>初始化项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建项目目录</span></span><br><span class="line"><span class="built_in">mkdir</span> goweb</span><br><span class="line"><span class="built_in">cd</span> goweb</span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始化go mode</span></span><br><span class="line">go mod init dev.net.cn/goweb</span><br></pre></td></tr></table></figure><p>执行完成后，会生成一个<code>go.mod</code>文件，用来管理项目的依赖和版本。</p><h3 id="配置代理"><a href="#配置代理" class="headerlink" title="配置代理"></a>配置代理</h3><p>为了更快的下载Go的模块，这里推荐大陆用户配置代理</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 如果是 Linux / macOS</span></span><br><span class="line"><span class="built_in">export</span> GOPROXY=https://goproxy.cn,direct</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果是 Windows (PowerShell)</span></span><br><span class="line"><span class="variable">$env</span>:GOPROXY=<span class="string">&quot;https://goproxy.cn,direct&quot;</span></span><br></pre></td></tr></table></figure><h3 id="下载gin依赖"><a href="#下载gin依赖" class="headerlink" title="下载gin依赖"></a>下载gin依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go get -u github.com/gin-gonic/gin</span><br></pre></td></tr></table></figure><h3 id="编写主程序代码"><a href="#编写主程序代码" class="headerlink" title="编写主程序代码"></a>编写主程序代码</h3><p>在项目根目录创建<code>main.go</code>，其内容如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 创建一个默认的Gin路由引擎（内置了Logger和Recovery）</span></span><br><span class="line">r := gin.Default()</span><br><span class="line">    <span class="comment">// 创建一个新的Gin路由引擎（不带任何中间件）</span></span><br><span class="line"><span class="comment">// r := gin.New()</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 绑定路由及操作 @Mapping(&quot;/hello&quot;)</span></span><br><span class="line">r.GET(<span class="string">&quot;/hello&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;message&quot;</span>: <span class="string">&quot;hello go web&quot;</span>,</span><br><span class="line"><span class="string">&quot;status&quot;</span>:  <span class="string">&quot;ok&quot;</span>,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 指定端口，默认就是8080</span></span><br><span class="line">err := r.Run(<span class="string">&quot;:8080&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;start error...&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://localhost:8080/hello</span><br></pre></td></tr></table></figure><p>返回</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;message&quot;</span><span class="punctuation">:</span><span class="string">&quot;hello go web&quot;</span><span class="punctuation">,</span><span class="attr">&quot;status&quot;</span><span class="punctuation">:</span><span class="string">&quot;ok&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>其中<code>c.JSON(http.StatusOK, gin.H&#123;...&#125;)</code>表示返回JSON响应，<code>gin.H</code>本质上是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">map</span>[<span class="type">string</span>]any</span><br></pre></td></tr></table></figure><div class="note info simple"><p>Gin中的<code>gin.Default()</code>和<code>gin.New()</code>的主要区别：<code>gin.Default()</code>默认包含了<code>Logger</code>和<code>Recovery</code>中间件；<code>gin.New()</code>创建一个不带默认中间件的<code>engine</code>，需要自己手动添加中间件。暂时先用<code>Default</code>。</p></div><h2 id="路径参数：c-Param"><a href="#路径参数：c-Param" class="headerlink" title="路径参数：c.Param"></a>路径参数：c.Param</h2><p>类似于：<code>@PathVariable(&quot;id&quot;)</code>，假设要获取一个路径参数，可以使用如下方式：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">r.GET(<span class="string">&quot;/users/:id&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="comment">// 获取路径参数 id</span></span><br><span class="line">    id := c.Param(<span class="string">&quot;id&quot;</span>)</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>: id,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>访问</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://localhost:8080/users/666</span><br></pre></td></tr></table></figure><p>响应：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;id&quot;</span><span class="punctuation">:</span><span class="string">&quot;666&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>默认拿到的路径参数是<code>string</code>类型。如需其他类型需要自行转换。</p><h2 id="Query参数：c-Query-c-DefaultQuery"><a href="#Query参数：c-Query-c-DefaultQuery" class="headerlink" title="Query参数：c.Query&#x2F;c.DefaultQuery"></a>Query参数：c.Query&#x2F;c.DefaultQuery</h2><p>类似于：<code>@ReuqestParam(&quot;xxx&quot;)</code>如果是Query参数，那么就需要如下方式：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">r.GET(<span class="string">&quot;/users&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="comment">// 如果参数不存在，返回默认值</span></span><br><span class="line">page := c.DefaultQuery(<span class="string">&quot;page&quot;</span>, <span class="string">&quot;1&quot;</span>)</span><br><span class="line">pageSize := c.DefaultQuery(<span class="string">&quot;page_size&quot;</span>, <span class="string">&quot;10&quot;</span>)</span><br><span class="line"><span class="comment">// 参数不存在 返回空字符串</span></span><br><span class="line">keyword := c.Query(<span class="string">&quot;keyword&quot;</span>)</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;page&quot;</span>:      page,</span><br><span class="line"><span class="string">&quot;page_size&quot;</span>: pageSize,</span><br><span class="line"><span class="string">&quot;keyword&quot;</span>:   keyword,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ul><li>DefaultQuery ： 如果不存在，返回默认值</li><li>Query： 如果不存在，返回空字符串。</li></ul><p>路径参数与Query参数的使用区别：</p><ul><li>路径参数通常表示资源标识，例如<code>/users/:id</code>；</li><li>Query参数通常表示过略、分页、排序条件。</li></ul><h2 id="POST-PUT-DELETE请求"><a href="#POST-PUT-DELETE请求" class="headerlink" title="POST&#x2F;PUT&#x2F;DELETE请求"></a>POST&#x2F;PUT&#x2F;DELETE请求</h2><p>上面的例子都是<code>GET</code>请求，下面介绍其他几个请求方式。本质上和Spring的没啥区别。</p><h3 id="POST"><a href="#POST" class="headerlink" title="POST"></a>POST</h3><p>对于<code>POST</code>，首先需要参数绑定，计划明天学习，先来实践一下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// binding： &quot;required 表示该字段必填，否则报错 对应Java @NotNull&quot;</span></span><br><span class="line">Name <span class="type">string</span> <span class="string">`json:&quot;name&quot; binding:&quot;required&quot;`</span></span><br><span class="line"><span class="comment">// 限制年龄在0-130</span></span><br><span class="line">Age <span class="type">int</span> <span class="string">`json:&quot;age&quot; binding:&quot;gte=0,lte=130&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserURI <span class="keyword">struct</span> &#123;</span><br><span class="line">ID <span class="type">string</span> <span class="string">`uri:&quot;id&quot; binding:&quot;required&quot;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">r.POST(<span class="string">&quot;/users&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> newUser User</span><br><span class="line"><span class="comment">//c.ShouldBindJSON(&amp;newUser); 相当于 @RequestBody</span></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;newUser); err != <span class="literal">nil</span> &#123;</span><br><span class="line">c.JSON(http.StatusBadRequest, gin.H&#123;</span><br><span class="line"><span class="string">&quot;error&quot;</span>: err.Error(),</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;status&quot;</span>: <span class="string">&quot;user created&quot;</span>,</span><br><span class="line"><span class="string">&quot;data&quot;</span>:   newUser,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><code>c.ShouldBindJSON(&amp;newUser)</code>相当于Spring中的<code>@ReuqestBody</code>，将请求体中的json绑定到User中。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">POST http<span class="punctuation">:</span><span class="comment">//localhost:8080/users</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tom&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">22</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="PUT"><a href="#PUT" class="headerlink" title="PUT"></a>PUT</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">r.PUT(<span class="string">&quot;/users/:id&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> uriData UserURI</span><br><span class="line"><span class="keyword">var</span> updateData User</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindUri(&amp;uriData); err != <span class="literal">nil</span> &#123;</span><br><span class="line">c.JSON(http.StatusBadRequest, gin.H&#123;</span><br><span class="line"><span class="string">&quot;error&quot;</span>: err.Error(),</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;updateData); err != <span class="literal">nil</span> &#123;</span><br><span class="line">c.JSON(http.StatusBadRequest, gin.H&#123;</span><br><span class="line"><span class="string">&quot;error&quot;</span>: err.Error(),</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;status&quot;</span>:  <span class="string">&quot;user updated&quot;</span>,</span><br><span class="line"><span class="string">&quot;userId&quot;</span>:  uriData.ID,</span><br><span class="line"><span class="string">&quot;newData&quot;</span>: updateData,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>代码综合GET&#x2F;POST</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">PUT http:<span class="comment">//localhost:8080/users/123</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;name&quot;</span>:<span class="string">&quot;jerry&quot;</span>,</span><br><span class="line">    <span class="string">&quot;age&quot;</span>: <span class="number">18</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Delete"><a href="#Delete" class="headerlink" title="Delete"></a>Delete</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">r.DELETE(<span class="string">&quot;/users/:id&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">id := c.Param(<span class="string">&quot;id&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> id == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">c.JSON(http.StatusBadRequest, gin.H&#123;</span><br><span class="line"><span class="string">&quot;error&quot;</span>: <span class="string">&quot;id is required&quot;</span>,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;status&quot;</span>: <span class="string">&quot;user deleted&quot;</span>,</span><br><span class="line"><span class="string">&quot;id&quot;</span>:     id,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>请求报文</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE http://localhost:8080/users/123</span><br></pre></td></tr></table></figure><h2 id="路由分组：RouterGroup"><a href="#路由分组：RouterGroup" class="headerlink" title="路由分组：RouterGroup"></a>路由分组：RouterGroup</h2><p>可以将某一类接口进行分组。相当于<code>Java</code>加在<code>Controller</code>类上的<code>@Mapping(&quot;/xxx&quot;)</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 创建一个默认的Gin路由引擎（内置了Logger和Recovery）</span></span><br><span class="line">r := gin.Default()</span><br><span class="line">api := r.Group(<span class="string">&quot;/api/v1&quot;</span>)</span><br><span class="line">&#123;</span><br><span class="line">api.GET(<span class="string">&quot;/users&quot;</span>, listUser)</span><br><span class="line">api.GET(<span class="string">&quot;/users/:id&quot;</span>, getUser)</span><br><span class="line">api.POST(<span class="string">&quot;/users&quot;</span>, createUser)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">err := r.Run(<span class="string">&quot;:8080&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;start error...&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">listUser</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;items&quot;</span>: []gin.H&#123;</span><br><span class="line">&#123;<span class="string">&quot;id&quot;</span>: <span class="number">1</span>, <span class="string">&quot;name&quot;</span>: <span class="string">&quot;tom&quot;</span>&#125;,</span><br><span class="line">&#123;<span class="string">&quot;id&quot;</span>: <span class="number">2</span>, <span class="string">&quot;name&quot;</span>: <span class="string">&quot;jack&quot;</span>&#125;,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getUser</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">id := c.Param(<span class="string">&quot;id&quot;</span>)</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>:   id,</span><br><span class="line"><span class="string">&quot;name&quot;</span>: <span class="string">&quot;tom&quot;</span>,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createUser</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">c.JSON(http.StatusOK, gin.H&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>:   <span class="number">1</span>,</span><br><span class="line"><span class="string">&quot;name&quot;</span>: <span class="string">&quot;tom&quot;</span>,</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>请求：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET http://localhost:8080/api/v1/users</span><br></pre></td></tr></table></figure><p>响应：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;items&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tom&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jack&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>路由分组适合做<code>API版本</code>、<code>模块分区</code>、<code>权限分区</code>。</p>]]>
    </content>
    <id>https://dev.net.cn/learning-go-web-01/</id>
    <link href="https://dev.net.cn/learning-go-web-01/"/>
    <published>2026-06-01T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天开始学习Go Web，目前主流的框架有<code>gin</code>、<code>go-zero</code>、<code>echo</code>等。我还是随主流，选择<code>gin</code>。</p>
<p>今天要学习的知识点是：</p>
<ul>
<li>]]>
    </summary>
    <title>Go Web学习笔记（一）- 路由、参数、JSON 响应</title>
    <updated>2026-06-01T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="VSCode" scheme="https://dev.net.cn/categories/VSCode/"/>
    <category term="VSCode" scheme="https://dev.net.cn/tags/vscode/"/>
    <content>
      <![CDATA[<h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>在<code>VS Code</code>中，点击<code>账户</code>-&gt;<code>登录以同步设置</code>后，弹出的登陆界面提示<code>404</code>，右下角错误信息为：<code>no_network_connectivity: No network connectivity. Check your internet connection.</code>。如下图所示：</p><span id="more"></span><p><img src="https://cdn.dev.net.cn/images/2026/04/vscode-login-404.webp!full" alt="vscode-login-404"></p><p>在mac&#x2F;linux上这个功能是可用的。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>其根本原因是<code>Clash Verge</code>在作祟，但也不能每次都关闭，还要查到具体的原因。后来通过多方搜索，找到了原因：</p><div class="note info simple"><p>登录调用的是<code>工作或学校账户</code>的UWP应用，默认无法使用本地回环地址的代理</p></div><p>所以，需要借助<code>Clash Verge</code>自带的<code>UWP 工具</code>，在<code>设置</code> -&gt; <code>Clash设置</code> -&gt; <code>UWP 工具</code>。打开后，勾选列表中的<code>工作或学校账户</code>。</p><p><img src="https://cdn.dev.net.cn/images/2026/04/vscode-login-uwp.webp!full" alt="vscode-login-uwp"></p><p>点击<code>Save Changes</code>后，再次尝试登录，就可以正常登录了。</p><p><img src="https://cdn.dev.net.cn/images/2026/04/vscode-login-success.webp!full" alt="vscode-login-success"></p>]]>
    </content>
    <id>https://dev.net.cn/fix-vscode-login-error/</id>
    <link href="https://dev.net.cn/fix-vscode-login-error/"/>
    <published>2026-04-13T03:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>在<code>VS Code</code>中，点击<code>账户</code>-&gt;<code>登录以同步设置</code>后，弹出的登陆界面提示<code>404</code>，右下角错误信息为：<code>no_network_connectivity: No network connectivity. Check your internet connection.</code>。如下图所示：</p>]]>
    </summary>
    <title>VS Code无法登录微软账号</title>
    <updated>2026-04-13T03:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<h2 id="文件操作：os、io、bufio"><a href="#文件操作：os、io、bufio" class="headerlink" title="文件操作：os、io、bufio"></a>文件操作：os、io、bufio</h2><p>Go语言中，文件操作主要分三层</p><ul><li>os : 直接和操作系统交互，比如打开文件、删除文件、创建文件、读写文件等。</li><li>io ：抽象I&#x2F;O行为，比如：io.Reader、io.Writer、io.Copy</li><li>bufio : 带缓冲的读写，适合按行读取、大文件处理、减少系统调用。</li></ul><h3 id="os-ReadFile"><a href="#os-ReadFile" class="headerlink" title="os.ReadFile"></a>os.ReadFile</h3><p>一次性读取整个文件，适合读取小文件，例如配置文件、模板文件、JSON文件。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOsReadFile</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">data, err := os.ReadFile(<span class="string">&quot;d:/a.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="type">string</span>(data))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>注意：不要用它读取特别大的文件，因为会一次性把整个文件加载到内存。</p></div><h3 id="os-WriteFile"><a href="#os-WriteFile" class="headerlink" title="os.WriteFile"></a>os.WriteFile</h3><p>一次性写入文件</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOsWriteFile</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">data := []<span class="type">byte</span>(<span class="string">&quot;Hello World\n&quot;</span>)</span><br><span class="line"></span><br><span class="line">err := os.WriteFile(<span class="string">&quot;d:/a1.txt&quot;</span>, data, <span class="number">0644</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="string">&quot;write success&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>0644</code>是权限标志，代表：<code>owner</code>:读写(6) 、<code>group</code>:只读(4)、<code>others:</code> 只读(4)，等同于linux中的<code>rw-r--r--</code>。另外：可执行权限是1(x)。</p><h3 id="os-Open"><a href="#os-Open" class="headerlink" title="os.Open"></a>os.Open</h3><p>打开文件：<code>os.Open</code>默认是只读打开。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOsOpen</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">file, err := os.Open(<span class="string">&quot;d:/a.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 关闭文件</span></span><br><span class="line"><span class="comment">//defer file.Close()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := file.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(file)</span><br><span class="line"></span><br><span class="line">t.Logf(<span class="string">&quot;file name: %s&quot;</span>, file.Name())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>打开文件后，一定要关闭，日常Go代码里，打开资源后立刻<code>defer Close()</code>是很常见的习惯。</p></div><p>不过，这里和<code>Java</code>一样，也要处理关闭时的异常。推荐使用如下代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := file.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(file)</span><br></pre></td></tr></table></figure><h3 id="os-Create"><a href="#os-Create" class="headerlink" title="os.Create"></a>os.Create</h3><p>创建文件：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOsCreate</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">file, err := os.Create(<span class="string">&quot;d:/a2.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := file.Close(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Logf(<span class="string">&quot;close file failed : %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(file)</span><br><span class="line"></span><br><span class="line">_, err = file.WriteString(<span class="string">&quot;hello world \n&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>如果文件已经存在，会清空源文件内容。</p></div><h3 id="os-OpenFile"><a href="#os-OpenFile" class="headerlink" title="os.OpenFile"></a>os.OpenFile</h3><p>追加写入文件：日常开发里的日志、追加数据等场景可以用到。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOsOpenFile</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">file, err := os.OpenFile(<span class="string">&quot;a.txt&quot;</span>, os.O_CREATE|os.O_WRONLY|os.O_APPEND, <span class="number">0644</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := file.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(file)</span><br><span class="line"></span><br><span class="line">_, err = file.WriteString(<span class="string">&quot;\n666666666666666666666666666\n&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中第二个参数是<code>flag</code>，常见的有：</p><ul><li><code>os.O_CREATE</code> : 文件不存在则创建</li><li><code>os.O_WRONLY</code> ：只写</li><li><code>os.O_RDWR</code> ：读写</li><li><code>os.O_APPEND</code> : 追加写</li><li><code>os.O_TRUNC</code>: 打开时清空</li></ul><h3 id="os-ErrNotExist"><a href="#os-ErrNotExist" class="headerlink" title="os.ErrNotExist"></a>os.ErrNotExist</h3><p>判断文件是否存在</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOsErrNotExist</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">_, err := os.Stat(<span class="string">&quot;ab.txt&quot;</span>)</span><br><span class="line"><span class="comment">//if err != nil &#123;</span></span><br><span class="line"><span class="comment">//t.Error(err)</span></span><br><span class="line"><span class="comment">//return</span></span><br><span class="line"><span class="comment">//&#125;</span></span><br><span class="line"><span class="comment">// 推荐使用errors.Is判断错误类型</span></span><br><span class="line"><span class="keyword">if</span> errors.Is(err, os.ErrNotExist) &#123;</span><br><span class="line">t.Error(<span class="string">&quot;file does not exist&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">t.Log(<span class="string">&quot;stat file&quot;</span>, err)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="io-Reader和io-Writer"><a href="#io-Reader和io-Writer" class="headerlink" title="io.Reader和io.Writer"></a>io.Reader和io.Writer</h3><p>这是Go语言关于I&#x2F;O的核心抽象</p><p><code>io.Reader</code>的定义大概如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Reader <span class="keyword">interface</span> &#123;</span><br><span class="line">    Read(p []<span class="type">byte</span>) (n <span class="type">int</span>,err <span class="type">error</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>io.Writer</code>的定义大概如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Writer <span class="keyword">interface</span> &#123;</span><br><span class="line">    Write(p []<span class="type">byte</span>) (n <span class="type">int</span>,err <span class="type">error</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>io</code>包的主要作用就是把各种<code>I/O</code>实现抽象成通用接口，例如<code>文件</code>、<code>网络链接</code>、<code>HTTP响应体</code>、<code>字符串reader</code>都可以被统一处理。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> 这个函数可以接受任何io.Reader</span></span><br><span class="line"><span class="comment">    printAll(file)</span></span><br><span class="line"><span class="comment">    printAll(resp.Body)</span></span><br><span class="line"><span class="comment">    printAll(strings.NewReader(&quot;abc&quot;))</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printAll</span><span class="params">(r io.Reader)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">data, err := io.ReadAll(r)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(<span class="type">string</span>(data))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIOReader</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">r := strings.NewReader(<span class="string">&quot;Hello Reader&quot;</span>)</span><br><span class="line">err := printAll(r)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这就是<code>Go</code>的接口设计风格：小接口、非常灵活。</p><h3 id="io-Copy"><a href="#io-Copy" class="headerlink" title="io.Copy"></a>io.Copy</h3><p>复制数据流，比如把一个我呢见复制到另一个文件：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIOCopy</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">src, err := os.Open(<span class="string">&quot;a.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := src.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(src)</span><br><span class="line"></span><br><span class="line">dest, err := os.Create(<span class="string">&quot;b.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := file.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(dest)</span><br><span class="line"></span><br><span class="line">n, err := io.Copy(dest, src)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">t.Log(<span class="string">&quot;copied bytes &quot;</span>, n)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用场景：<code>文件复制</code>、<code>HTTP 下载保存到文件</code>、<code>上传文件写入磁盘</code>、<code>把请求体转发给另一个服务</code>等。</p><h3 id="bufio-Scanner"><a href="#bufio-Scanner" class="headerlink" title="bufio.Scanner"></a>bufio.Scanner</h3><p>按行读取文件，这是处理日志、CSV、文本文件时非常常用的写法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestBufioScanner</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">file, err := os.Open(<span class="string">&quot;info.log&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := file.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(file)</span><br><span class="line"></span><br><span class="line">scanner := bufio.NewScanner(file)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Scanner默认单个token大小有限，如果读取超长行或者大日志等，需要调大buffer</span></span><br><span class="line"><span class="comment">// scanner.Buffer(make([]byte ,1024),1024*1024)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> scanner.Scan() &#123;</span><br><span class="line">line := scanner.Text()</span><br><span class="line">t.Log(line)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := scanner.Err(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>bufio</code>会包装一个<code>io.Reader</code>或<code>io.Writer</code>，提供缓冲能力和更方便的文本<code>I/O</code>操作。</p><div class="note info simple"><p><code>Scanner</code>默认单个token大小有限。如果你读取超长行，可能需要调大buffer。</p></div><h3 id="bufio-Writer"><a href="#bufio-Writer" class="headerlink" title="bufio.Writer"></a>bufio.Writer</h3><p>缓冲写入</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestBufioWriter</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">file, err := os.Create(<span class="string">&quot;bufio_writer.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(file *os.File)</span></span> &#123;</span><br><span class="line">err := file.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(file)</span><br><span class="line"></span><br><span class="line">writer := bufio.NewWriter(file)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line">_, err := writer.WriteString(fmt.Sprintf(<span class="string">&quot;line %d\n&quot;</span>, i))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 注意，这里需要flush，否则不会落盘</span></span><br><span class="line"><span class="keyword">if</span> err := writer.Flush(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>使用<code>bufio.Writer</code>后，数据可能还在缓冲区里，不调用<code>Flush()</code>可能不会真正写入文件。</p></div><p>一句话总结：</p><div class="note info simple"><p><code>os</code>负责具体的文件和操作系统交互，<code>io</code>提供通用<code>I/O</code>抽象，核心时<code>io.Reader</code>和<code>io.Writer</code>，<code>bufio</code>在Reader&#x2F;Writer外层增加缓冲，适合按行读取和批量写入。小文件可以使用<code>os.ReadFile</code>，大文件或流式处理应该用<code>os.Open</code>配合<code>bufio.Scanner</code>、<code>bufio.Reader</code>或者<code>io.Copy</code>。</p></div><h2 id="JSON-处理：encoding-json"><a href="#JSON-处理：encoding-json" class="headerlink" title="JSON 处理：encoding&#x2F;json"></a>JSON 处理：encoding&#x2F;json</h2><p>Go标准库的<code>encoding/json</code>用于<code>JSON</code>编码和解码，也是Go对象与JSON字符串之间的转换。常见操作有：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">json.Marshal               : Go struct/map -&gt; JSON bytes</span><br><span class="line">json.Unmarshal             : JSON bytes  -&gt; Go struct/map</span><br><span class="line">json.NewEncoder            : 把JSON写到io.Writer</span><br><span class="line">json.NewDecoder            : 从io.Reader 读取 JSON</span><br></pre></td></tr></table></figure><p>先定义一个<code>user</code>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">ID   <span class="type">int</span>    <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">Name <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">Age  <span class="type">int</span>    <span class="string">`json:&quot;age&quot;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="json-Marshal"><a href="#json-Marshal" class="headerlink" title="json.Marshal"></a>json.Marshal</h3><p>从<code>struct/map</code> 转JSON</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestJsonMarsha1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := User&#123;</span><br><span class="line">ID:   <span class="number">1</span>,</span><br><span class="line">Name: <span class="string">&quot;tom&quot;</span>,</span><br><span class="line">Age:  <span class="number">18</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data, err := json.Marshal(u)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">t.Log(<span class="type">string</span>(data))</span><br><span class="line"></span><br><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]any&#123;</span><br><span class="line"><span class="string">&quot;id&quot;</span>:    <span class="number">1</span>,</span><br><span class="line"><span class="string">&quot;age&quot;</span>:   <span class="number">18</span>,</span><br><span class="line"><span class="string">&quot;phone&quot;</span>: <span class="number">1323232</span>,</span><br><span class="line"><span class="string">&quot;name&quot;</span>:  <span class="string">&quot;Jerry&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dataMap, err := json.Marshal(m)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">t.Log(<span class="type">string</span>(dataMap))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;id&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;tom&quot;</span><span class="punctuation">,</span><span class="attr">&quot;age&quot;</span><span class="punctuation">:</span><span class="number">18</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;age&quot;</span><span class="punctuation">:</span><span class="number">18</span><span class="punctuation">,</span><span class="attr">&quot;id&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;Jerry&quot;</span><span class="punctuation">,</span><span class="attr">&quot;phone&quot;</span><span class="punctuation">:</span><span class="number">1323232</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="json-Unmarshal"><a href="#json-Unmarshal" class="headerlink" title="json.Unmarshal"></a>json.Unmarshal</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestJsonUnmarshal</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">input := []<span class="type">byte</span>(<span class="string">`&#123;&quot;id&quot;:1,&quot;name&quot;:&quot;tom&quot;,&quot;age&quot;:18&#125;`</span>)</span><br><span class="line"><span class="keyword">var</span> u User</span><br><span class="line"><span class="keyword">var</span> m <span class="keyword">map</span>[<span class="type">string</span>]any</span><br><span class="line"></span><br><span class="line">err := json.Unmarshal(input, &amp;u)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">t.Logf(<span class="string">&quot;%+v\n&quot;</span>, u)</span><br><span class="line">    <span class="comment">// 通常建议 err复用即可，因为本身就是一个临时变量。</span></span><br><span class="line">err = json.Unmarshal(input, &amp;m)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">t.Logf(<span class="string">&quot;%+v\n&quot;</span>, m)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;ID:1 Name:tom Age:18&#125;</span><br><span class="line">map[age:18 <span class="built_in">id</span>:1 name:tom]</span><br></pre></td></tr></table></figure><p>从上述例子可以看到，<code>json.Unmarshal(input,&amp;u)</code>这里必须传递指针，因为要修改<code>u</code>的值。</p><h3 id="JSON-tag"><a href="#JSON-tag" class="headerlink" title="JSON tag"></a>JSON tag</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UserInfo <span class="keyword">struct</span> &#123;</span><br><span class="line">ID   <span class="type">int</span>    <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">UserName <span class="type">string</span> <span class="string">`json:&quot;user_name&quot;`</span></span><br><span class="line">Password  <span class="type">int</span>    <span class="string">`json:&quot;-&quot;`</span></span><br><span class="line">    Email    <span class="type">string</span>  <span class="string">`json:&quot;email,omitempty&quot;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>含义：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">json:<span class="string">&quot;id&quot;</span>               JSON字段名是<span class="built_in">id</span></span><br><span class="line">json:<span class="string">&quot;uer_name&quot;</span>         JSON字段名是user_name</span><br><span class="line">json:<span class="string">&quot;-&quot;</span>                忽略该字段</span><br><span class="line">json:<span class="string">&quot;email,omitempty&quot;</span>  空值时忽略</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestJsonTag</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := UserInfo&#123;</span><br><span class="line">ID:       <span class="number">1</span>,</span><br><span class="line">UserName: <span class="string">&quot;tom&quot;</span>,</span><br><span class="line">Password: <span class="string">&quot;123456&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">s, err := json.Marshal(u)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">t.Log(<span class="type">string</span>(s))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;id&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span><span class="attr">&quot;user_name&quot;</span><span class="punctuation">:</span><span class="string">&quot;tom&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><div class="note info simple"><p>注意：<code>Go</code>的JSON序列化只能处理导出字段，也就是大写开头的字段。</p></div><p>下面的这个就无法序列化成功。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UserInfo1 <span class="keyword">struct</span> &#123;</span><br><span class="line">id   <span class="type">int</span>    <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">name <span class="type">string</span> <span class="string">`json:&quot;user_name&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestJsonTag1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := UserInfo1&#123;</span><br><span class="line">id:   <span class="number">1</span>,</span><br><span class="line">name: <span class="string">&quot;tom&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">s, err := json.Marshal(u)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 输出 &#123;&#125;</span></span><br><span class="line">t.Log(<span class="type">string</span>(s))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Decoder-Encoder"><a href="#Decoder-Encoder" class="headerlink" title="Decoder&#x2F;Encoder"></a>Decoder&#x2F;Encoder</h3><p>适合HTTP和流式处理，例如在HTTP handler里，经常会这样写：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">json.NewDecoder(r.Body).Decode(&amp;req)</span><br><span class="line">json.NewEncoder(w).Encode(resp)</span><br></pre></td></tr></table></figure><p>例如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> CreateUserRequest <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">Age  <span class="type">int</span>    <span class="string">`json:&quot;age&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CreateUserResponse <span class="keyword">struct</span> &#123;</span><br><span class="line">ID   <span class="type">int</span>    <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">Name <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createUserHandler</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> req CreateUserRequest</span><br><span class="line"><span class="keyword">if</span> err := json.NewDecoder(r.Body).Decode(&amp;req); err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, <span class="string">&quot;invalid json&quot;</span>, http.StatusBadRequest)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">resp := CreateUserResponse&#123;</span><br><span class="line">ID:   <span class="number">1</span>,</span><br><span class="line">Name: req.Name,</span><br><span class="line">&#125;</span><br><span class="line">w.Header().Set(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err := json.NewEncoder(w).Encode(resp); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Encoder</code>&#x2F;<code>Decoder</code>的好处是可以直接对接<code>io.Reader</code>和<code>io.Writer</code>，不用先手动转换为<code>[]byte</code>。</p><h3 id="DisallowUnknownFields"><a href="#DisallowUnknownFields" class="headerlink" title="DisallowUnknownFields"></a>DisallowUnknownFields</h3><p>拒绝未知字段：默认情况下，Go会忽略<code>JSON</code>里的未知字段。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span>“name”<span class="punctuation">:</span><span class="string">&quot;Tom&quot;</span><span class="punctuation">,</span><span class="attr">&quot;age&quot;</span><span class="punctuation">:</span><span class="number">20</span><span class="punctuation">,</span><span class="attr">&quot;unknown&quot;</span><span class="punctuation">:</span><span class="string">&quot;xxx&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>如果<code>struct</code>里没有<code>unknown</code>字段，默认不会报错。</p><p>如果需要严格校验：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">decoder := json.NewDecoder(r.Body)</span><br><span class="line"><span class="comment">// </span></span><br><span class="line">decoder.DisallowUnknownFields()</span><br></pre></td></tr></table></figure><p>在<code>API</code>开发里很有用，可以避免前端传错字段。</p><p>除了上述提到的<code>小写无法序列化（非导出）</code>和<code>Unmarshal</code>需要传递指针外，还有一个需要注意的就是数字会被解析为<code>flaot64</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> m <span class="keyword">map</span>[<span class="type">string</span>]any</span><br><span class="line">json.Unmarshal([]<span class="type">byte</span>(<span class="string">`&#123;&quot;id&quot;:123&#125;`</span>),&amp;m)</span><br><span class="line"><span class="comment">// float64</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;%T\n&quot;</span>,m[<span class="string">&quot;id&quot;</span>])</span><br></pre></td></tr></table></figure><p>如果需要精确处理数字，可以使用<code>struct</code>，或者使用<code>Decoder.UseNumber()</code>。</p><h2 id="HTTP-服务：net-http"><a href="#HTTP-服务：net-http" class="headerlink" title="HTTP 服务：net&#x2F;http"></a>HTTP 服务：net&#x2F;http</h2><p><code>net/http</code>是Go标准库里非常重要的包，它同时提供HTTP client和 HTTP server实现。</p><h3 id="一个简单的HTTP服务"><a href="#一个简单的HTTP服务" class="headerlink" title="一个简单的HTTP服务"></a>一个简单的HTTP服务</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">helloHandler</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">_, err := fmt.Fprintln(w, <span class="string">&quot;hello go&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestNet</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/hello&quot;</span>, helloHandler)</span><br><span class="line"></span><br><span class="line">err := http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="http-ResponseWriter和-http-Request"><a href="#http-ResponseWriter和-http-Request" class="headerlink" title="http.ResponseWriter和 *http.Request"></a>http.ResponseWriter和 *http.Request</h3><p>handle的核心签名是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(w http.ResponseWriter,r * http.Request)</span></span></span><br></pre></td></tr></table></figure><p>可以理解为：</p><ul><li><code>w</code>:用来写响应</li><li><code>r</code>：表示当前请求</li></ul><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">userHandler</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">method := r.Method</span><br><span class="line">path := r.URL.Path</span><br><span class="line">query := r.URL.Query().Get(<span class="string">&quot;name&quot;</span>)</span><br><span class="line"></span><br><span class="line">fmt.Println(method, path, query)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在上一个例子中，添加如下：</span></span><br><span class="line">http.HandleFunc(<span class="string">&quot;/user&quot;</span>, userHandler)</span><br></pre></td></tr></table></figure><p>然后再<code>Goland</code>中的<code>rest-api.http</code>的请求中执行如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET http://localhost:8080/user?name=zhangsan</span><br></pre></td></tr></table></figure><h3 id="按Method区分请求"><a href="#按Method区分请求" class="headerlink" title="按Method区分请求"></a>按Method区分请求</h3><p>将<code>userHandler</code>方法修改为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">userHandler</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> r.Method &#123;</span><br><span class="line"><span class="keyword">case</span> http.MethodGet:</span><br><span class="line">w.Header().Set(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>)</span><br><span class="line">json.NewEncoder(w).Encode([]User&#123;</span><br><span class="line">&#123;ID: <span class="number">1</span>, Name: <span class="string">&quot;tom&quot;</span>&#125;,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">case</span> http.MethodPost:</span><br><span class="line"><span class="keyword">var</span> req User</span><br><span class="line"><span class="keyword">if</span> err := json.NewDecoder(r.Body).Decode(&amp;req); err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, <span class="string">&quot;invaild json&quot;</span>, http.StatusBadRequest)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">req.ID = <span class="number">100</span></span><br><span class="line">w.Header().Set(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>)</span><br><span class="line">w.WriteHeader(http.StatusCreated)</span><br><span class="line">err := json.NewEncoder(w).Encode(req)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">http.Error(w, <span class="string">&quot;method not allowed&quot;</span>, http.StatusMethodNotAllowed)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自定义http-Server"><a href="#自定义http-Server" class="headerlink" title="自定义http.Server"></a>自定义http.Server</h3><p><code>http.ListenAndSarver(&quot;:8080&quot;,nil)</code>通常也只是用在demo阶段，实际工作中还是会显示的创建<code>http.Server</code>，配置超时时间。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHttpServer</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">mux := http.NewServeMux()</span><br><span class="line">mux.HandleFunc(<span class="string">&quot;/health&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">_, err := w.Write([]<span class="type">byte</span>(<span class="string">&quot;ok&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">server := &amp;http.Server&#123;</span><br><span class="line">Addr:         <span class="string">&quot;:8080&quot;</span>,</span><br><span class="line">Handler:      mux,</span><br><span class="line">ReadTimeout:  <span class="number">5</span> * time.Second,</span><br><span class="line">WriteTimeout: <span class="number">10</span> * time.Second,</span><br><span class="line">IdleTimeout:  <span class="number">60</span> * time.Second,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">log.Println(<span class="string">&quot;server start at :8080&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := server.ListenAndServe(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="HTTP-Client"><a href="#HTTP-Client" class="headerlink" title="HTTP Client"></a>HTTP Client</h3><p>HTTP客户端：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHttpClient</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">client := &amp;http.Client&#123;</span><br><span class="line">Timeout: <span class="number">5</span> * time.Second,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resp, err := client.Get(<span class="string">&quot;https://localhost:8080/health&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;request failed: &quot;</span>, err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 关闭的模板代码</span></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(resp *http.Response)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := (*resp).Body.Close(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;close response body failed: &quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;(resp)</span><br><span class="line"></span><br><span class="line">body, err := io.ReadAll(resp.Body)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;read body failed &quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(<span class="string">&quot;status: &quot;</span>, resp.StatusCode)</span><br><span class="line">fmt.Println(<span class="type">string</span>(body))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和读取文件一样（本质上也是IO流），HTTP响应体必须关闭，否则会造成连接泄露。</p><h3 id="带Context的请求"><a href="#带Context的请求" class="headerlink" title="带Context的请求"></a>带Context的请求</h3><p>日常开发，更推荐使用<code>context</code>控制超时和取消</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestContext</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ctx, cancel := context.WithTimeout(context.Background(), <span class="number">2</span>*time.Second)</span><br><span class="line"><span class="keyword">defer</span> cancel()</span><br><span class="line">req, err := http.NewRequestWithContext(ctx, http.MethodGet, <span class="string">&quot;https://localhost:8080/health&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;new request failed &quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">resp, err := http.DefaultClient.Do(req)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;request failed : &quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(resp *http.Response)</span></span> &#123;</span><br><span class="line">err := resp.Body.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;close failed &quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;(resp)</span><br><span class="line">data, _ := io.ReadAll(resp.Body)</span><br><span class="line">fmt.Println(<span class="type">string</span>(data))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="用httptest测试HTTP-handler"><a href="#用httptest测试HTTP-handler" class="headerlink" title="用httptest测试HTTP handler"></a>用httptest测试HTTP handler</h3><p><code>net/http/httptest</code>是测试HTTP handler的标准工具。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">helloHandler1</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">w.WriteHeader(http.StatusOK)</span><br><span class="line">_, err := w.Write([]<span class="type">byte</span>(<span class="string">&quot;hello&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;resp error&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHelloHandler1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">req := httptest.NewRequest(http.MethodGet, <span class="string">&quot;/hello&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line">rec := httptest.NewRecorder()</span><br><span class="line"></span><br><span class="line">helloHandler1(rec, req)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> rec.Code != http.StatusOK &#123;</span><br><span class="line">t.Fatalf(<span class="string">&quot;expected status 200, got %d &quot;</span>, rec.Code)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> rec.Body.String() != <span class="string">&quot;hello&quot;</span> &#123;</span><br><span class="line">t.Fatalf(<span class="string">&quot;unexpected body : %s &quot;</span>, rec.Body.String())</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于网络</p><div class="note info simple"><p><code>net/http</code>同时提供<code>HTTP</code>服务端和客户端。服务端核心是<code>http.Handler</code>，常见的方法签名是<code>func(w http.ResponseWriter, r *http.Request)。</code> <code>ResponseWriter</code> 用来写响应，<code>Request</code>表示请求。通常编码时推荐使用自定义<code>http.Server</code>并且设置<code>timeout</code>。客户端请求后必须关闭<code>resp.Body</code>，并且推荐使用<code>context</code>控制超时和取消。</p></div><h2 id="字符串处理：strings、strconv"><a href="#字符串处理：strings、strconv" class="headerlink" title="字符串处理：strings、strconv"></a>字符串处理：strings、strconv</h2><p><code>strings</code>用来处理字符串查找、切割、拼接、转换、裁剪等操作；<code>strconv</code>用于字符串和基础类型之间的转换。例如 <code>string</code>转换为<code>int</code>、<code>int</code>转<code>string</code>等。</p><h3 id="strings-Contains"><a href="#strings-Contains" class="headerlink" title="strings.Contains"></a>strings.Contains</h3><p>判断是否包含</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStringsContains</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;hello go&quot;</span></span><br><span class="line">t.Log(strings.Contains(s, <span class="string">&quot;ll&quot;</span>))</span><br><span class="line">t.Log(strings.Contains(s, <span class="string">&quot;golang&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strings-HasPrefix-strings-HasSuffix"><a href="#strings-HasPrefix-strings-HasSuffix" class="headerlink" title="strings.HasPrefix&#x2F;strings.HasSuffix"></a>strings.HasPrefix&#x2F;strings.HasSuffix</h3><p>前缀和后缀</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHasPrefix</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">url := <span class="string">&quot;https://www.google.com&quot;</span></span><br><span class="line">t.Log(strings.HasPrefix(url, <span class="string">&quot;https&quot;</span>))</span><br><span class="line">t.Log(strings.HasSuffix(url, <span class="string">&quot;.com&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strings-Split-strings-Join"><a href="#strings-Split-strings-Join" class="headerlink" title="strings.Split&#x2F;strings.Join"></a>strings.Split&#x2F;strings.Join</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSplit</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;go,java,python,typescript&quot;</span></span><br><span class="line">parts := strings.Split(s, <span class="string">&quot;,&quot;</span>)</span><br><span class="line"><span class="keyword">for</span> _, part := <span class="keyword">range</span> parts &#123;</span><br><span class="line">t.Log(part)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// []string  -&gt; string</span></span><br><span class="line">joined := strings.Join(parts, <span class="string">&quot;|&quot;</span>)</span><br><span class="line"><span class="comment">// go|java|python|typescript</span></span><br><span class="line">t.Log(joined)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strings-TrimSpace"><a href="#strings-TrimSpace" class="headerlink" title="strings.TrimSpace"></a>strings.TrimSpace</h3><p>去掉首位空白</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTripSpace</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;   Hello  Go    &quot;</span></span><br><span class="line"><span class="comment">//Hello  Go</span></span><br><span class="line">t.Log(strings.TrimSpace(s))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strings-ReplaceAll"><a href="#strings-ReplaceAll" class="headerlink" title="strings.ReplaceAll"></a>strings.ReplaceAll</h3><p>替换所有字符串</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestReplaceALl</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;Hello Go&quot;</span></span><br><span class="line">t.Log(strings.ReplaceAll(s, <span class="string">&quot;Go&quot;</span>, <span class="string">&quot;Golang&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strings-Builder"><a href="#strings-Builder" class="headerlink" title="strings.Builder"></a>strings.Builder</h3><p>拼接字符串，类似Java里的<code>StringBuilder</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestBuilder</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> sb strings.Builder</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line">sb.WriteString(<span class="string">&quot;GO&quot;</span>)</span><br><span class="line">sb.WriteString(<span class="string">&quot; &quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">t.Log(sb.String())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这玩意也是为了解决大量字符串拼接的问题。解决<code>string += &quot;xxx&quot;</code>的问题。</p><h3 id="len-string-统计字节数"><a href="#len-string-统计字节数" class="headerlink" title="len(string)统计字节数"></a>len(string)统计字节数</h3><p>这个在第二天的只是中学习过，注意：它统计的是字节数，而不是字符数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLen</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;你好，Go&quot;</span></span><br><span class="line"><span class="keyword">for</span> i, r := <span class="keyword">range</span> s &#123;</span><br><span class="line">t.Log(i, <span class="type">string</span>(r))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  strings_test.go:55: 0 你</span></span><br><span class="line"><span class="comment">  strings_test.go:55: 3 好</span></span><br><span class="line"><span class="comment">  strings_test.go:55: 6 ，</span></span><br><span class="line"><span class="comment">  strings_test.go:55: 9 G</span></span><br><span class="line"><span class="comment">  strings_test.go:55: 10 o</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 5  如何需要按字符数</span></span><br><span class="line">t.Log(<span class="built_in">len</span>([]<span class="type">rune</span>(s)))</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strconv-Atoi-Itoa"><a href="#strconv-Atoi-Itoa" class="headerlink" title="strconv.Atoi&#x2F;Itoa"></a>strconv.Atoi&#x2F;Itoa</h3><p>这个也是第二天的内容</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStrconv</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;123&quot;</span></span><br><span class="line">n, err := strconv.Atoi(s)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(<span class="string">&quot;convert failed&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//  string: 123 ,convert int</span></span><br><span class="line">t.Logf(<span class="string">&quot;string: %s ,convert %T&quot;</span>, s, n)</span><br><span class="line"></span><br><span class="line">num := <span class="number">888</span></span><br><span class="line">str := strconv.Itoa(num)</span><br><span class="line">t.Logf(<span class="string">&quot;str type : %T, value : %s&quot;</span>, str, str)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="strconv-ParseInt-FormatInt"><a href="#strconv-ParseInt-FormatInt" class="headerlink" title="strconv.ParseInt&#x2F;FormatInt"></a>strconv.ParseInt&#x2F;FormatInt</h3><p>如果需要控制进制和位数，用<code>ParseInt</code>(<code>ParseFloat</code>、<code>ParseBool</code>、<code>ParseComplex</code>、<code>ParseUint</code>)</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestParseInt</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;123&quot;</span></span><br><span class="line">n, err := strconv.ParseInt(s, <span class="number">10</span>, <span class="number">64</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(<span class="string">&quot;convert failed&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">t.Logf(<span class="string">&quot;string: %s ,convert %T&quot;</span>, s, n)</span><br><span class="line"></span><br><span class="line">s1 := strconv.FormatInt(n, <span class="number">10</span>)</span><br><span class="line">t.Logf(<span class="string">&quot;str type : %T, value : %s&quot;</span>, s1, s1)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中：<code>123</code>是输出的字符串，<code>10</code>： <code>十进制</code>，<code>64</code>：<code>int64</code></p><h2 id="单元测试：testing-包"><a href="#单元测试：testing-包" class="headerlink" title="单元测试：testing 包"></a>单元测试：testing 包</h2><p>Go的测试不需要<code>JUnit</code>这种外部框架，标准库自带<code>testing</code>包，配合<code>go test</code>命令就可以完成测试。例如我大量的例子都是，其规则为：1、文件名，要以<code>_test.go</code>结尾，函数必须是<code>func TestXxx(*testing.T)</code>。</p><p>例如：<code>math_test.go</code>,创建一个<code>加法</code>代码:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Add</span><span class="params">(a, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMath</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">got := Add(<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line">want := <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> got != want &#123;</span><br><span class="line">t.Fatalf(<span class="string">&quot;Add(1,2) = %d,want %d&quot;</span>, got, want)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="t-Error和t-Fatal的区别"><a href="#t-Error和t-Fatal的区别" class="headerlink" title="t.Error和t.Fatal的区别"></a>t.Error和t.Fatal的区别</h3><p><code>t.Error</code>&#x2F;<code>t.Errorf</code>：报错但继续</p><p><code>t.Fatal</code>&#x2F;<code>t.Fatalf</code>：保存并停止当前测试</p><h3 id="表驱动测试"><a href="#表驱动测试" class="headerlink" title="表驱动测试"></a>表驱动测试</h3><p>日常开发中非常重要的写法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestAdd</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">tests := []<span class="keyword">struct</span> &#123;</span><br><span class="line">name <span class="type">string</span></span><br><span class="line">a    <span class="type">int</span></span><br><span class="line">b    <span class="type">int</span></span><br><span class="line">want <span class="type">int</span></span><br><span class="line">&#125;&#123;</span><br><span class="line">&#123;name: <span class="string">&quot;positive number&quot;</span>, a: <span class="number">1</span>, b: <span class="number">2</span>, want: <span class="number">3</span>&#125;,</span><br><span class="line">&#123;name: <span class="string">&quot;zero&quot;</span>, a: <span class="number">0</span>, b: <span class="number">5</span>, want: <span class="number">5</span>&#125;,</span><br><span class="line">&#123;name: <span class="string">&quot;negative number&quot;</span>, a: <span class="number">-1</span>, b: <span class="number">1</span>, want: <span class="number">0</span>&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, tt := <span class="keyword">range</span> tests &#123;</span><br><span class="line">t.Run(tt.name, <span class="function"><span class="keyword">func</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">got := Add(tt.a, tt.b)</span><br><span class="line"><span class="keyword">if</span> got != tt.want &#123;</span><br><span class="line">t.Fatalf(<span class="string">&quot;Add(%d,%d) = %d,want %d&quot;</span>, tt.a, tt.b, got, tt.want)</span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-go-08/</id>
    <link href="https://dev.net.cn/learning-go-08/"/>
    <published>2026-04-07T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="文件操作：os、io、bufio"><a href="#文件操作：os、io、bufio" class="headerlink" title="文件操作：os、io、bufio"></a>文件操作：os、io、bufio</h2><p>Go语言中，文件操作主要分三]]>
    </summary>
    <title>Go语言学习笔记（八）- 标准库</title>
    <updated>2026-04-07T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<h2 id="error-接口与-errors-包"><a href="#error-接口与-errors-包" class="headerlink" title="error 接口与 errors 包"></a>error 接口与 errors 包</h2><p>Go语言的错误，不像<code>Java</code>&#x2F;<code>Python</code>那样默认靠异常传播机制，而是一个普通返回值。Go内置的<code>error</code>类型本质上是一个<code>接口</code>。只要求实现一个方法<code>Error() string</code>；<code>nil</code>通常表示没有错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="type">error</span> <span class="keyword">interface</span> &#123;</span><br><span class="line">    Error() <span class="type">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在Go代码里最常见的模板就是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">result,err := doSomething()</span><br><span class="line"><span class="keyword">if</span> err !=<span class="literal">nil</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种模式，就相当于<code>Java</code>的<code>try/catch</code>。</p><h3 id="创建简单错误"><a href="#创建简单错误" class="headerlink" title="创建简单错误"></a>创建简单错误</h3><p><code>errors.New(text)</code>会创建一个只包含文本信息的<code>error</code>；每次调用<code>errors.New</code>都返回一个不同的<code>error</code>的值，即使文本相同。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">divide</span><span class="params">(a, b <span class="type">int</span>)</span></span> (<span class="type">int</span>, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> b == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>, errors.New(<span class="string">&quot;division by zero&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> a / b, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorNew</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">v, err := divide(<span class="number">4</span>, <span class="number">2</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(v)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>日常开发中，<code>errors.New</code>适合创建<code>简单</code>、<code>固定</code>、<code>无须携带上下文</code>的错误。</p><h3 id="创建带有格式化信息的错误"><a href="#创建带有格式化信息的错误" class="headerlink" title="创建带有格式化信息的错误"></a>创建带有格式化信息的错误</h3><p>如果错误信息需要拼接变量，那就需要使用<code>fmt.Errorf</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">findUser</span><span class="params">(id <span class="type">int</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;user %d not found&quot;</span>, id)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorFindUser</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">err := findUser(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="错误包装"><a href="#错误包装" class="headerlink" title="错误包装"></a>错误包装</h3><p>实际开发中，不止返回底层错误，还需要加上下文。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;read config %s failed %w&quot;</span>,path,err)</span><br></pre></td></tr></table></figure><p><code>%w</code>的意思就是：包装原始错误。使用<code>%w</code>且参数时<code>error</code>时，返回的错误会实现<code>unwrap</code>，从而可以被<code>errors.Is</code>和<code>errors.As</code>检查。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">readConfig</span><span class="params">(path <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">_, err := os.Open(path)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="comment">// 注意这里一定是%w，千万不要写成%v,%v 只是把错误转成字符串，原始错误链会丢失。</span></span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;read config %s failed : %w&quot;</span>, path, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorReadConfig</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">err := readConfig(<span class="string">&quot;config.yaml&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line"><span class="keyword">if</span> errors.Is(err, os.ErrNotExist) &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;config file does not exist&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出</span></span><br><span class="line"><span class="comment">// read config config.yaml failed : open config.yaml: The system cannot find the file specified.</span></span><br><span class="line"><span class="comment">// config file does not exist</span></span><br></pre></td></tr></table></figure><p><code>%v</code>和<code>%w</code>有啥区别呢？</p><ul><li><code>%v</code>：只格式化错误文本。</li><li><code>%w</code>：包装<code>error</code>，保留错误链，可以使用<code>errors.Is</code>和<code>errors.As</code>判断。</li></ul><h3 id="errors-Is-判断错误链里是否包含某个错误"><a href="#errors-Is-判断错误链里是否包含某个错误" class="headerlink" title="errors.Is 判断错误链里是否包含某个错误"></a>errors.Is 判断错误链里是否包含某个错误</h3><p><code>errors.Is(err,target)</code>用来判断<code>err</code>或者它包装的底层错误里，是否包含指定错误。通常也是推荐使用<code>errors.Is</code>来检查错误链，而不是<code>==</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// sentinel error</span></span><br><span class="line"><span class="keyword">var</span> ErrUserNotFound = errors.New(<span class="string">&quot;user not found&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getUser</span><span class="params">(id <span class="type">int</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;query user %d failed：%w&quot;</span>, id, ErrUserNotFound)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorGetUser</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">err := getUser(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> errors.Is(err, ErrUserNotFound) &#123;</span><br><span class="line">t.Log(<span class="string">&quot;handle user not found&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>如果错误可能被包装过，不应该使用<code>err == ErrUserNotFound</code>，而应该使用<code>errors.Is(err,ErrUserNotFound)</code>。</p></div><h3 id="errors-As-提取某种具体错误类型"><a href="#errors-As-提取某种具体错误类型" class="headerlink" title="errors.As:提取某种具体错误类型"></a>errors.As:提取某种具体错误类型</h3><p><code>errors.As</code>用来从错误链中找出某种具体类型的错误。<code>errors.As</code>会检查整条错误链。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">parseAge</span><span class="params">(s <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">_, err := strconv.Atoi(s)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;parse age failed: %w&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorParseAge</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">err := parseAge(<span class="string">&quot;age&quot;</span>)</span><br><span class="line"><span class="keyword">var</span> numErr *strconv.NumError</span><br><span class="line">    <span class="comment">// errors.As需要把找到的错误复制到变量里</span></span><br><span class="line"><span class="keyword">if</span> errors.As(err, &amp;numErr) &#123;</span><br><span class="line"><span class="comment">// invalid number : age</span></span><br><span class="line">fmt.Println(<span class="string">&quot;invalid number :&quot;</span>, numErr.Num)</span><br><span class="line"><span class="comment">// function : Atoi</span></span><br><span class="line">fmt.Println(<span class="string">&quot;function :&quot;</span>, numErr.Func)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那么<code>errors.Is</code>和<code>errors.As</code>有啥区别呢？</p><ul><li><code>errors.Is</code> : 判断是不是某个目标错误，常用于<code>sentinel error</code></li><li><code>errors.As</code>：判断能不能转换成某个错误类型，并提取出来。</li></ul><h3 id="errors-Unwrap-手动拆开错误链"><a href="#errors-Unwrap-手动拆开错误链" class="headerlink" title="errors.Unwrap: 手动拆开错误链"></a>errors.Unwrap: 手动拆开错误链</h3><p><code>errors.Unwrap(err)</code>会调用错误上的<code>Unwrap() error</code>方法，如果没有，就返回<code>nil</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorsUnwrap</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">base := errors.New(<span class="string">&quot;base error&quot;</span>)</span><br><span class="line">wrapped := fmt.Errorf(<span class="string">&quot;service failed: %w&quot;</span>, base)</span><br><span class="line">    <span class="comment">// service failed: base error</span></span><br><span class="line">fmt.Println(wrapped)</span><br><span class="line">inner := errors.Unwrap(wrapped)</span><br><span class="line">    <span class="comment">// base error</span></span><br><span class="line">fmt.Println(inner)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通常来说，更推荐使用<code>errors.Is</code>和<code>errors.As</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">errors.Is(err, target)</span><br><span class="line">errors.As(err, &amp;target)</span><br></pre></td></tr></table></figure><h3 id="errors-Join-合并多个错误"><a href="#errors-Join-合并多个错误" class="headerlink" title="errors.Join 合并多个错误"></a>errors.Join 合并多个错误</h3><p><code>errors.Join</code>可以把多个<code>error</code>合并成一个<code>error</code>：<code>Join</code>会丢弃<code>nil</code>，如果全部都是<code>nil</code>，则返回<code>nil</code>，非<code>nil</code>的<code>Join</code>结果可以通过<code>errors.Is</code>和<code>errors.As</code>检查。</p><p>例如：批量处理时，不想遇到第一个错误就返回，而是收集所有错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">validateName</span><span class="params">(name <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> name == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;name is empty&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">validateAge</span><span class="params">(age <span class="type">int</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> age &lt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;age must be greater than zero&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">validateUser</span><span class="params">(name <span class="type">string</span>, age <span class="type">int</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">var</span> errs []<span class="type">error</span></span><br><span class="line"><span class="keyword">if</span> err := validateName(name); err != <span class="literal">nil</span> &#123;</span><br><span class="line">errs = <span class="built_in">append</span>(errs, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err := validateAge(age); err != <span class="literal">nil</span> &#123;</span><br><span class="line">errs = <span class="built_in">append</span>(errs, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> errors.Join(errs...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestErrorsJoin</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">err := validateUser(<span class="string">&quot;&quot;</span>, <span class="number">-1</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">//    error_test.go:131: name is empty</span></span><br><span class="line"><span class="comment">//    age must be greater than zero</span></span><br><span class="line">t.Log(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>适合场景：</p><ul><li>批量校验</li><li>批量删除</li><li>批量导入</li><li>多个资源清理失败</li></ul><div class="note info simple"><p>Go的<code>error</code>是一个内置接口，只有<code>Error() string</code>方法。Go通过多返回值显示返回错误，通常使用<code>if nil != nil</code>处理。简单错误可以用<code>errors.New</code>，带上下文的错误可以用<code>fmt.Errorf</code>。如果要保留底层错误链，应该使用<code>%w</code>包装，再通过<code>errors.Is</code>判断<code>sentinel Error</code>,通过<code>errors.As</code>提取具体错误类型。</p></div><h2 id="自定义错误类型"><a href="#自定义错误类型" class="headerlink" title="自定义错误类型"></a>自定义错误类型</h2><p>自定义错误类型的核心是：只要事先<code>Error() string</code>方法，就实现了error接口。</p><p>对于<code>Java</code>来说</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyException</span> <span class="keyword">extends</span> <span class="title class_">Exception</span></span><br></pre></td></tr></table></figure><p>如果是<code>Go</code>，则只需要：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e MyError)</span></span> Error() <span class="type">string</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;xxxxx&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一个简单的例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UserNotFound <span class="keyword">struct</span> &#123;</span><br><span class="line">UserID <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e UserNotFound)</span></span> Error() <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Sprintf(<span class="string">&quot;user %d not found&quot;</span>, e.UserID)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">findUserByUserId</span><span class="params">(id <span class="type">int</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> UserNotFound&#123;UserID: id&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestError1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">err := findUserByUserId(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Log(err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>自定义错误类型适合如下场景：</p><ol><li>错误需要携带结构化字段</li><li>调用方需要根据字段做判断</li><li>需要保留底层错误</li><li>业务系统有错误码</li><li>API层需要把内部错误映射为HTTP状态码</li></ol><p>例如：把业务是错误转为HTTP响应</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> AppError <span class="keyword">struct</span> &#123;</span><br><span class="line">Code       <span class="type">string</span></span><br><span class="line">Message    <span class="type">string</span></span><br><span class="line">HTTPStatus <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *AppError)</span></span> Error() <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> e.Code + <span class="string">&quot; : &quot;</span> + e.Message</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ErrUserNotFound2 = &amp;AppError&#123;</span><br><span class="line">Code:       <span class="string">&quot;USER_NOT_FOUND&quot;</span>,</span><br><span class="line">Message:    <span class="string">&quot;user not found&quot;</span>,</span><br><span class="line">HTTPStatus: http.StatusNotFound,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getUser1</span><span class="params">(id <span class="type">int</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> id == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> ErrUserNotFound2</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handle</span><span class="params">()</span></span> &#123;</span><br><span class="line">err := getUser1(<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> appErr *AppError</span><br><span class="line"><span class="keyword">if</span> errors.As(err, &amp;appErr) &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;status: &quot;</span>, appErr.HTTPStatus)</span><br><span class="line">fmt.Println(<span class="string">&quot;code: &quot;</span>, appErr.Code)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="string">&quot;status: &quot;</span>, http.StatusInternalServerError)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>在Go中，自定义错误类型只要实现<code>Error() string</code>就实现了<code>error</code>接口。如果错误需要携带结构化信息，比如错误码、资源ID、路径、HTTP状态码，就适合使用自定义错误类型。如果自定义错误类内部包装了底层错误，应实现<code>Unwrap()</code>，这样调用方仍然可以使用<code>errors.Is</code>和<code>error.As</code>检查错误链。</p></div><h2 id="go-mod"><a href="#go-mod" class="headerlink" title="go mod"></a>go mod</h2><p>Go的现代项目基本都是用<code>module</code>。一个<code>module</code>有<code>go.mod</code>文件定义，<code>go.mod</code>会描述当前模块路径、Go版本和依赖模块等信息。相当于<code>Java</code>的<code>pom.xml</code>&#x2F;<code>build.gradle</code>。</p><h3 id="go-mod-init"><a href="#go-mod-init" class="headerlink" title="go mod init"></a>go mod init</h3><p>初始化模块，<code>go mod init</code>会在当前目录初始化并写入新的<code>go.mod</code>文件。</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir learn<span class="literal">-go</span></span><br><span class="line"><span class="built_in">cd</span> learn<span class="literal">-go</span></span><br><span class="line">go mod init dev.net.cn/learn<span class="literal">-go</span></span><br></pre></td></tr></table></figure><p>此时就会在<code>learn-go</code>目录下生成一个<code>go.mod</code>文件，内容为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">module dev.net.cn/learn-<span class="keyword">go</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="number">1.26</span><span class="number">.3</span></span><br></pre></td></tr></table></figure><p>其中<code>module dev.net.cn/learn-go</code>就是模块路径，真实目录通常写仓库地址：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">github.com/xxx/xxx</span><br><span class="line"><span class="comment">// 例如本站dev.net.cn/learn-go </span></span><br><span class="line"><span class="comment">// 和Java不同的是，这里的域名不需要倒着写，因为Go 的依赖管理工具会根据模块路径去真实的服务器拉取代码</span></span><br><span class="line">company.com/xxx/xxx</span><br></pre></td></tr></table></figure><p>如果没有自己的域名，那就可以使用<code>github</code>仓库来命名（这种比较推荐），很多Go的模块都是如此。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 例如我想做个gin-json的模块，就可以使用如下方式，然后在github创建仓库名为gin-json即可。</span><br><span class="line"><span class="keyword">go</span> mod init github.com/idevly/gin-json</span><br></pre></td></tr></table></figure><p>尽可能不要随便写模块名：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> mod init test</span><br></pre></td></tr></table></figure><p>因为后续包导入路径会基于<code>module path</code>。</p><p>初始体验，个人觉得不如maven。</p><h3 id="module、package、repository的关系"><a href="#module、package、repository的关系" class="headerlink" title="module、package、repository的关系"></a>module、package、repository的关系</h3><p>Go程序由<code>package</code>组织；一个<code>package</code>是同一目录下共同编译的一组<code>GO源文件</code>。一个<code>repository</code>可以包含一个或多个<code>module</code>，而一个<code>module</code>是一起发布的一组相关<code>package</code>。通过下面一个结构就能理解：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">repository 仓库</span><br><span class="line">  └── module 模块，go.mod 所在目录</span><br><span class="line">        └── package 包，一个目录通常一个 package</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">gin-json/</span><br><span class="line">  <span class="keyword">go</span>.mod                 module dev.net.cn/gin-json</span><br><span class="line">  main.<span class="keyword">go</span>                <span class="keyword">package</span> main</span><br><span class="line">  internal/</span><br><span class="line">    service/</span><br><span class="line">      todo_service.<span class="keyword">go</span>    <span class="keyword">package</span> service</span><br><span class="line">    repository/</span><br><span class="line">      todo_repo.<span class="keyword">go</span>       <span class="keyword">package</span> repository</span><br></pre></td></tr></table></figure><p>如果要导入路径：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;dev.net.cn/gin-json/internal/service&quot;</span></span><br></pre></td></tr></table></figure><h3 id="go-sum"><a href="#go-sum" class="headerlink" title="go.sum"></a>go.sum</h3><p>添加完以来后，Go会生成<code>go.sum</code>，里面记录依赖模块的校验和，用来验证下载的模块文件完整性。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> get github.com/google/uuid</span><br></pre></td></tr></table></figure><p>在代码里使用：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> day7</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/google/uuid&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoMod</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">t.Log(uuid.NewString())</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>此时，项目根目录下面会存在两个文件<code>go.mod</code>和<code>go.sum</code>，这两个文件都需要提交到git仓库。</p><h3 id="go-mod-tidy"><a href="#go-mod-tidy" class="headerlink" title="go mod tidy"></a>go mod tidy</h3><p>整理依赖：<code>go mod tidy</code>会让<code>go.mod</code>和源码实际使用的依赖保持一致。他会添加缺失的依赖，删除不再提供相关包的依赖，并更新<code>go.sum</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">go get github.com/gin-gonic/gin</span><br><span class="line"><span class="comment"># 如果代码里没有任何使用gin的地方，但是它存在在go.mod中，那么执行此命令就会删掉这个模块，并且更新go.sum</span></span><br><span class="line">go mod tidy</span><br><span class="line"><span class="comment"># 反之，如果代码里使用了某个模块，但没有下载这个模块，执行go mod tidy也会自动下载。</span></span><br></pre></td></tr></table></figure><h3 id="indirect依赖"><a href="#indirect依赖" class="headerlink" title="indirect依赖"></a>indirect依赖</h3><p>在<code>go.mod</code>里可以看到</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">require github.com/some/lib v1<span class="number">.2</span><span class="number">.3</span> <span class="comment">// indirect</span></span><br></pre></td></tr></table></figure><p>这个意思就是：当前项目代码没有直接<code>import</code>它，但它是你得直接依赖的依赖。<code>//indirect</code>表示这个目录没有提供主模块中包直接导入的package。  类比就是Java第三方库依赖的其他依赖。</p><p>这个不需要自己维护，所以无需上心，知道是啥意思就行。</p><h3 id="go-mod-vendor"><a href="#go-mod-vendor" class="headerlink" title="go mod vendor"></a>go mod vendor</h3><p>把依赖复制到<code>vendor</code>目录：<code>go mod vendor</code>会在主模块根目录创建<code>vendor</code>目录，复制构建和测试当前模块所需的依赖包，他会生成<code>vendor/modules.txt</code>，记录依赖包和版本。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">go mod vendor</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">d----          2026/6/3     10:21                  day7</span><br><span class="line">d----          2026/6/3     10:33                  vendor</span><br><span class="line">-a---          2026/6/3     10:27             65   go.mod</span><br><span class="line">-a---          2026/6/3     10:27            163   go.sum</span><br><span class="line"><span class="comment"># vendor里面就是如下结构</span></span><br><span class="line"> D:\GolandProjects\hellogo\vendor\github.com\google\uuid</span><br></pre></td></tr></table></figure><p><code>vendor/modules.txt</code>内容为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># github.com/google/uuid v1.6.0</span></span><br><span class="line"><span class="comment">## explicit</span></span><br><span class="line">github.com/google/uuid</span><br></pre></td></tr></table></figure><p>然后使用<code>vendor</code>构建：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 显示声明</span></span><br><span class="line">go build -mod=vendor ./...</span><br><span class="line">go <span class="built_in">test</span> -mod=vendor ./...</span><br><span class="line"><span class="comment"># go 1.14以后 默认即可，它会自动使用vendor</span></span><br><span class="line">go build ./...</span><br><span class="line">go <span class="built_in">test</span> ./...</span><br></pre></td></tr></table></figure><p>需要使用<code>vendor</code>的场景，基本上就是如下：</p><ol><li>内网环境，不能访问外网（to G）</li><li>构建环境要求所有依赖在仓库里</li><li>对供应链安全又强约束</li><li>老项目</li><li>希望构建时不访问网络。</li></ol><p>记得以前在生产使用<code>jenkins</code>构建部署前端项目时，直接将npm的<code>node_modules</code>上传到服务器上，Maven也是要将<code>.m2</code>仓库上传到指定服务器上。就这意思~~</p><h3 id="replace-本地调试依赖"><a href="#replace-本地调试依赖" class="headerlink" title="replace 本地调试依赖"></a>replace 本地调试依赖</h3><p>如果有两个项目</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">user-service</span><br><span class="line">common-lib</span><br></pre></td></tr></table></figure><p><code>user-service/go.mod</code>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">module dev.net.cn/user-service</span><br><span class="line">require dev.net.cn/common-lib v1<span class="number">.0</span><span class="number">.0</span></span><br><span class="line"></span><br><span class="line">replace dev.net.cn/common-lib =&gt; ./common-lib</span><br></pre></td></tr></table></figure><p>这样本地开发时，<code>user-service</code>就会使用本地的<code>common-lib</code>。</p><div class="note info simple"><p>注意，提交代码的时候，<code>replace</code>是否需要提交，自己要特别做一下判断。理论上都不应该提交。</p></div><h2 id="包的组织与可见性（大小写）"><a href="#包的组织与可见性（大小写）" class="headerlink" title="包的组织与可见性（大小写）"></a>包的组织与可见性（大小写）</h2><p>对于Java来说，Java包往往都比较深：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">src/main/java/cn/net/dev/user-cms/user/service/UserService.java</span><br></pre></td></tr></table></figure><p>而Go更偏向：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">internal/service/user.go</span><br></pre></td></tr></table></figure><div class="note info simple"><p>Go的基本单位是<code>package</code>，一个<code>package</code>是同一个目录中，一起编译的一组<code>Go源文件</code>,同一个<code>package</code>的不同文件可以互相访问不辞定义的函数、类型、变量和常量。</p></div><p>还有一个特别要注意的：</p><div class="note info simple"><p>同一个文件夹（package）下，所有源文件必须属于同一个 <code>package</code>（包）</p></div><p>例如在<code>service</code>这个包或者是目录下面，所有的<code>.go</code>源码中，package都必须是<code>service</code>，不能有些是<code>package service</code>有些是<code>package main</code>。当然，<code>_test.go</code>包是个例外。</p><p>所以，这里也是建议，往后的go项目结构应当是如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">my_project/</span><br><span class="line">  ├── <span class="keyword">go</span>.mod</span><br><span class="line">  ├── cmd/</span><br><span class="line">  │   └── my_app/</span><br><span class="line">  │       └── main.<span class="keyword">go</span>    <span class="comment">// package main（单独的目录）</span></span><br><span class="line">  └── internal/ (或直接在根目录)</span><br><span class="line">      └── service/</span><br><span class="line">          └── todo_service.<span class="keyword">go</span> <span class="comment">// package service</span></span><br></pre></td></tr></table></figure><p>如果一个目录要编译成可执行程序，包名必须是<code>package main</code>。并且通常有<code>func main()&#123;&#125;</code>方法。例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">my-api/</span><br><span class="line">  cmd/</span><br><span class="line">    server/</span><br><span class="line">      main.<span class="keyword">go</span></span><br></pre></td></tr></table></figure><p><code>main.go</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello World&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go run ./cmd/server</span><br></pre></td></tr></table></figure><p>正如上面提到的，通常建议把入口放在<code>cmd</code>目录下(尤其是仓库中有多个命令时)：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cmd/server/main.go</span><br><span class="line">cmd/migrate/main.go</span><br><span class="line">cmd/worker/main.go</span><br></pre></td></tr></table></figure><h3 id="大写导出，小写私有"><a href="#大写导出，小写私有" class="headerlink" title="大写导出，小写私有"></a>大写导出，小写私有</h3><p>这是Go可见性的核心。</p><div class="note info simple"><p>Go规范规定：一个标识符如果首字母是<code>Unicode大写字母</code>，并且它声明在<code>package block</code>中，或者是<code>字段名</code>&#x2F;<code>方法名</code>，那么他就是<code>exported</code>；其他标识符都不是<code>exported</code>。</p></div><p>通俗讲就是</p><ul><li>大写开头，包外可访问</li><li>小写开头，只能在当前package内可访问。</li></ul><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> user</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// 大写</span></span><br><span class="line">ID   <span class="type">int</span></span><br><span class="line">Name <span class="type">string</span></span><br><span class="line"><span class="comment">// 小写</span></span><br><span class="line">password <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 大写</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewUser</span><span class="params">(name <span class="type">string</span>)</span></span> User &#123;</span><br><span class="line"><span class="keyword">return</span> User&#123;</span><br><span class="line">ID:       <span class="number">1</span>,</span><br><span class="line">Name:     name,</span><br><span class="line">password: <span class="string">&quot;123456&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 小写</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">validateName</span><span class="params">(name <span class="type">string</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> name != <span class="string">&quot;&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其他包可以访问：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">user.User</span><br><span class="line">user.NewUser</span><br><span class="line">u.ID</span><br><span class="line">u.Name</span><br></pre></td></tr></table></figure><p>但不能访问</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">u.password</span><br><span class="line">user.validateName</span><br></pre></td></tr></table></figure><h3 id="struct字段大小写也影响JSON"><a href="#struct字段大小写也影响JSON" class="headerlink" title="struct字段大小写也影响JSON"></a>struct字段大小写也影响JSON</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// 大写</span></span><br><span class="line">ID   <span class="type">int</span>  <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">Name <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line"><span class="comment">// 小写 结构体字段 &#x27;password&#x27; 具有 &#x27;json&#x27; 标记，但未被导出 </span></span><br><span class="line">password <span class="type">string</span> <span class="string">`json:&quot;password&quot;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然有了<code>json</code>标记，但小谢字段<code>password</code>对<code>encoding/json</code>也是不可导出的，外部包不能通过反射正常访问它，所以不会被正常序列化。当然，将他改成大写就可以了，但如果不想将敏感字段进行序列化，最好是不要将其放入<code>response struct</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UserResponse <span class="keyword">struct</span> &#123;</span><br><span class="line">ID   <span class="type">int</span>  <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">Name <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span>    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>应该和<code>Java</code>差不多，Go也会分<code>model</code>、<code>DTO</code>之类的分层。</p><h3 id="包名应该短小且不重复"><a href="#包名应该短小且不重复" class="headerlink" title="包名应该短小且不重复"></a>包名应该短小且不重复</h3><div class="note info simple"><p>Go包名一般短小、全小写</p></div><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> user</span><br><span class="line"><span class="keyword">package</span> service</span><br><span class="line"><span class="keyword">package</span> repository</span><br><span class="line"><span class="keyword">package</span> dto</span><br><span class="line"><span class="keyword">package</span> http</span><br></pre></td></tr></table></figure><p>不推荐</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> userService</span><br><span class="line"><span class="keyword">package</span> userservice</span><br><span class="line"><span class="keyword">package</span> user_service</span><br></pre></td></tr></table></figure><p>Go常见风格是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">user.NewService()</span><br><span class="line"><span class="comment">// 反例 userservice.NewUserService()</span></span><br><span class="line">repo.New()</span><br><span class="line">config.Load()</span><br></pre></td></tr></table></figure><h3 id="internal目录：限制包导入范围"><a href="#internal目录：限制包导入范围" class="headerlink" title="internal目录：限制包导入范围"></a>internal目录：限制包导入范围</h3><p><code>internal</code>是Go非常重要的工程化机制，位于<code>internal</code>目录或者其子目录下的代码，只能被<code>internal</code>父目录树内的代码导入。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">todo-api/</span><br><span class="line">  go.mod</span><br><span class="line">  internal/</span><br><span class="line">    service/</span><br><span class="line">      todo_service.go</span><br><span class="line">  cmd/</span><br><span class="line">    server/</span><br><span class="line">      main.go</span><br></pre></td></tr></table></figure><p><code>cmd/server/main.go</code>可以导入</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;dev.net.cn/todo-api/internal/service&quot;</span></span><br></pre></td></tr></table></figure><p>但另一个外部项目不能导入</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// use of internal package not allowed</span></span><br><span class="line"><span class="comment">// import &quot;dev.net.cn/todo-api/internal/service&quot;</span></span><br></pre></td></tr></table></figure><p>日常建议：</p><ul><li>internal&#x2F; 放项目内部实现</li><li>pkg&#x2F; 放希望别人import的公共库</li><li>cmd&#x2F; 放可执行程序入口</li></ul><p>例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">todo-api/</span><br><span class="line">  go.mod</span><br><span class="line">  cmd/</span><br><span class="line">    server/</span><br><span class="line">      main.go</span><br><span class="line">  internal/</span><br><span class="line">    handler/</span><br><span class="line">      todo_handler.go</span><br><span class="line">    service/</span><br><span class="line">      todo_service.go</span><br><span class="line">    repository/</span><br><span class="line">      todo_repository.go</span><br><span class="line">    model/</span><br><span class="line">      todo.go</span><br><span class="line">  pkg/</span><br><span class="line">    response/</span><br><span class="line">      response.go</span><br></pre></td></tr></table></figure><p>含义：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">cmd/server：程序入口</span><br><span class="line">internal/handler：HTTP handler</span><br><span class="line">internal/service：业务逻辑</span><br><span class="line">internal/repository：数据库访问</span><br><span class="line">internal/model：内部模型</span><br><span class="line">pkg/response：可复用公共包</span><br></pre></td></tr></table></figure><p>但也要注意，Go 不是 Java，不需要为了“架构感”建很多包。</p>]]>
    </content>
    <id>https://dev.net.cn/learning-go-07/</id>
    <link href="https://dev.net.cn/learning-go-07/"/>
    <published>2026-04-06T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="error-接口与-errors-包"><a href="#error-接口与-errors-包" class="headerlink" title="error 接口与 errors 包"></a>error 接口与 errors 包</h2><p>Go语言的错]]>
    </summary>
    <title>Go语言学习笔记（七）- 错误处理与包管理</title>
    <updated>2026-04-06T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<p>今天开始学习Go语言的重要特性，<code>Goroutine</code>。</p><h2 id="goroutine"><a href="#goroutine" class="headerlink" title="goroutine"></a>goroutine</h2><p><code>Goroutine</code>，通常称之为<code>协程</code>，区别于<code>Java</code>的线程，它是由<code>Go runtime</code>调度，启动成本很低，初始栈很小，并且可以按需增长。多个<code>goroutine</code>在同一个进程地址空间内并发执行。</p><p>先看看<code>Java</code>的线程：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;System.out.println(<span class="string">&quot;Hello Java&quot;</span>);&#125;).start();</span><br></pre></td></tr></table></figure><p>在看看看<code>Go</code>的<code>goroutine</code>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello Go&quot;</span>)</span><br><span class="line">&#125;()</span><br></pre></td></tr></table></figure><h3 id="启动普通函数"><a href="#启动普通函数" class="headerlink" title="启动普通函数"></a>启动普通函数</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;sync&quot;</span></span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrintName</span><span class="params">(name <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Println(name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    <span class="comment">// wg用来等待程序完成</span></span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">    <span class="comment">// 计数加1，表示要等待1个goroutine,注意，这里暂时不可以是2，因为其中一个没有执行wa.Done()，后续在解释。</span></span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="comment">//如果需要 defer wg.Done()，通常会包一层匿名函数。</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="comment">// 在函数退出时，调用Done来通知main函数工作已经完成</span></span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">PrintName(<span class="string">&quot;hello&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line"><span class="comment">// 也可以这样直接调用</span></span><br><span class="line"><span class="keyword">go</span> PrintName(<span class="string">&quot;Hello Goroutine&quot;</span>)</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="string">&quot;Waiting To Finish&quot;</span>)</span><br><span class="line">wg.Wait()</span><br><span class="line">fmt.Println(<span class="string">&quot;Terminating Program&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>如果你需要 <code>defer wg.Done()</code>，通常会包一层匿名函数。</p></div><p>此时程序会打印如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestGoroutine</span><br><span class="line">Waiting To Finish</span><br><span class="line">Hello Goroutine</span><br><span class="line">hello</span><br><span class="line">Terminating Program</span><br><span class="line">--- PASS: TestGoroutine (0.00s)</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure><h3 id="启动匿名函数"><a href="#启动匿名函数" class="headerlink" title="启动匿名函数"></a>启动匿名函数</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>&#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;running in background&quot;</span>)</span><br><span class="line">&#125;()</span><br></pre></td></tr></table></figure><p>特别注意，后面的<code>()</code>别忘了，这个括号表示立即执行这个匿名函数，只是他会在新的<code>goroutine</code>中执行。</p><h3 id="主goroutine退出，程序就结束"><a href="#主goroutine退出，程序就结束" class="headerlink" title="主goroutine退出，程序就结束"></a>主goroutine退出，程序就结束</h3><p>这是一个特别需要注意的地方。下面通过一个错误示例来观察：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;hello&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line">fmt.Println(<span class="string">&quot;main done&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个例子，最后打印的结果有三种情况：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 情况一，两个打印语句都执行</span></span><br><span class="line">main <span class="keyword">done</span></span><br><span class="line">hello</span><br><span class="line"></span><br><span class="line"><span class="comment"># 情况二，只打印 main done</span></span><br><span class="line">main <span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 情况三，极低概率，会先打印hello</span></span><br><span class="line">hello</span><br><span class="line">main <span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>和<code>Java</code>的线程类似，谁先执行（或者会不会执行），完全看CPU如何调度（如果是<code>主goroutine</code>先执行，那么程序就会退出，其他的<code>goroutine</code>不一定会执行），这是不可靠的。为了避免这个问题，请使用如下方式：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine3</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">fmt.Println(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WaitGroup"><a href="#WaitGroup" class="headerlink" title="WaitGroup"></a>WaitGroup</h2><div class="note info simple"><p><code>sync.WaitGroup</code>是日常开发最常用的<code>goroutine</code>等待工具。它本质上是一个计数器：<code>Add</code>增加任务数，<code>Done</code>表示任务完成，<code>Wait</code>阻塞等待计数归零。言简意赅就是用于等待一组<code>goroutine</code>或任务完成得计数号量。</p></div><p>有些像<code>CountDownLatch</code>?</p><p>上面的两个例子都已经有过介绍，其主要语法（其实就是模板代码）就是</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="comment">// 增加一个任务数</span></span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 任务完成后，计数器减少1</span></span><br><span class="line">    <span class="keyword">defer</span> wg.Done()</span><br><span class="line">&#125;()</span><br><span class="line"><span class="comment">// 等计数器等于0，则退出，否则会阻塞。最开始那个例子就是因为第二个没有执行Done，导致计数器不是0</span></span><br><span class="line">wg.Wait()</span><br></pre></td></tr></table></figure><p>这里也有个知识点需要说明，<code>wg.Add(1)</code>需要放在<code>goroutine</code>外面。下面先看要给错误的示例：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine4</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">fmt.Println(i)</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个例子得问题：<code>wg.Wait()</code>可能再<code>wg.Add(1)</code>执行前就开始等待，甚至直接结束。</p><p>需要将其修改为如下方式：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine5</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line"><span class="comment">// 在 go 关键字之前执行 Add，确保主协程去 Wait 时，计数器一定大于 0</span></span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(i <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">fmt.Println(i)</span><br><span class="line">&#125;(i)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 此时计数器是 3，主协程会老老实实在这里死等，直到 3 次 Done 都报到</span></span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>使用 <code>sync.WaitGroup</code> 的黄金法则是：<strong>必须在启动子协程之前（在主协程里）就把账记好（<code>Add</code>）,<code>Done</code>必须在go之中。</strong></p></div><p>上述例子还可以修改为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine6</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="comment">// 如果确定知道要执行三次，也可以再循环外一次性把计数器设置好</span></span><br><span class="line">wg.Add(<span class="number">3</span>)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(i <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">fmt.Println(i)</span><br><span class="line">&#125;(i)</span><br><span class="line">&#125;</span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于面试来说，只需要一句：</p><div class="note info simple"><p><code>Add</code> 必须在启动 goroutine 之前调用，否则主 goroutine 可能先执行到 <code>Wait</code>，造成等待计数不准确，甚至提前返回。</p></div><p>由于我是基于<code>Go 1.26</code>学习，这个版本对<code>WaitGroup.go</code>有所优化，可以直接使用<code>Go</code>方法，它可以直接启动任务并等待。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine7</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="comment">// 直接启动任务并等待</span></span><br><span class="line">wg.Go(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;task 1&quot;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 直接启动任务并等待</span></span><br><span class="line">wg.Go(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;task 2&quot;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从代码层面来看，使用<code>wg.Go()</code>肯定要优于传统写法得，如果使用传统写法，你需要特别注意：</p><ul><li>必须时刻小心<code>Add()</code>得位置，不能写在<code>go func</code>内部</li><li>必须保证<code>Add</code>数量与<code>Done</code>的数量绝对相等</li><li>任何地方漏写了<code>defer wg.Done()</code>，或者因为<code>panic</code>中途崩了导致<code>wa.Done()</code>没执行上，程序会直接死锁。</li></ul><p><strong>传统写法，用于面试或者维护古老项目使用。新项目建议使用<code>wg.Go(func()&#123;&#125;)</code></strong></p><div class="note info simple"><p>这里有个小坑：<code>WaitGroup</code>可以复用，但必须确保上一轮彻底结束后在进入下一轮，复杂场景下不如重新定义新的<code>WaitGroup</code>。</p></div><h2 id="channel"><a href="#channel" class="headerlink" title="channel"></a>channel</h2><p><code>goroutine</code>不能像普通函数那样直接拿返回值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不支持如下语法</span></span><br><span class="line"><span class="comment">//result := go calculate()</span></span><br></pre></td></tr></table></figure><p>此时就需要通过<code>channel</code>接受结果。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">calculate</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">100</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine8</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">resultChannel := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">resultChannel &lt;- calculate()</span><br><span class="line">&#125;()</span><br><span class="line">result := &lt;-resultChannel</span><br><span class="line">t.Logf(<span class="string">&quot;Result from goroutine: %d&quot;</span>, result)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者也可以通过<code>WaitGroup</code> + <code>共享变量</code>实现：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutine9</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="keyword">var</span> result <span class="type">int</span></span><br><span class="line"></span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">result = <span class="number">100</span></span><br><span class="line">&#125;()</span><br><span class="line">wg.Wait()</span><br><span class="line">t.Logf(<span class="string">&quot;Result from goroutine: %d&quot;</span>, result)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然也能拿到返回值，但如果有多个<code>goroutine</code>同时写共享变量，就得考虑<code>锁</code>或者<code>channel</code></p><div class="note info simple"><p>Go 有一句非常经典的思想：不要通过共享内存通信，而要通过通信共享内存。</p></div><p>Go 官方 <a href="https://go.dev/blog/codelab-share">blog</a> 也强调，Go 鼓励通过 <code>channel</code> 在 <code>goroutine</code> 之间传递数据引用，避免多个 <code>goroutine</code> 同时直接操作同一份数据。</p><p>对于Java程序员来说，channel可以类比为：</p><ul><li>Blocking Queue</li><li>线程间消息传递</li><li>生产者-消费者模式</li></ul><h3 id="基础发送和接收"><a href="#基础发送和接收" class="headerlink" title="基础发送和接收"></a>基础发送和接收</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestChannel</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 创建一个string类型的channel</span></span><br><span class="line">    ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="comment">// 表示发送</span></span><br><span class="line">ch &lt;- <span class="string">&quot;hello&quot;</span></span><br><span class="line">&#125;()</span><br><span class="line">    <span class="comment">// 表示接收</span></span><br><span class="line">msg := &lt;-ch</span><br><span class="line">t.Log(msg)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="无缓冲channel"><a href="#无缓冲channel" class="headerlink" title="无缓冲channel"></a>无缓冲channel</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br></pre></td></tr></table></figure><p>无缓冲<code>channel</code>发送时，如果没有接收者，发送方会阻塞。，总结其特点就是：</p><ul><li>发送方发送时，如果没有接收方准备好，会阻塞</li><li>接收方接收时，如果没有发送方准备好，也会阻塞</li></ul><p>所以，它不仅能传数据，还能用来做同步。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestChannel2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;sending...&quot;</span>)</span><br><span class="line">ch &lt;- <span class="number">1</span></span><br><span class="line">fmt.Println(<span class="string">&quot;sent&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line">v := &lt;-ch</span><br><span class="line">t.Logf(<span class="string">&quot;received,%d&quot;</span>, v)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>无缓冲<code>channel</code>很像<code>Java</code>里的<code>SynchronousQueue</code>，发送和接收必须“碰头”。</p><h3 id="有缓冲channel"><a href="#有缓冲channel" class="headerlink" title="有缓冲channel"></a>有缓冲channel</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 有缓冲channel 创建方式</span></span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>,<span class="number">2</span>)</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestChannel3</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 创建一个容量为2的缓冲channel</span></span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>, <span class="number">2</span>)</span><br><span class="line">    <span class="comment">// 前两次发送不会阻塞，第三次发送如果没人接收，就会阻塞</span></span><br><span class="line">ch &lt;- <span class="number">1</span></span><br><span class="line">ch &lt;- <span class="number">2</span></span><br><span class="line">t.Log(&lt;-ch)</span><br><span class="line">t.Log(&lt;-ch)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有缓冲<code>channel</code>在缓冲区没满前，发送不会阻塞。相当于<code>Java</code>的<code>BlockingQueue</code>。但<code>Go</code>的<code>channel</code>语义更轻，更适合配合<code>select</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ch := make(chan int, 2)</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">2</span>)</span><br></pre></td></tr></table></figure><h3 id="关闭channel"><a href="#关闭channel" class="headerlink" title="关闭channel"></a>关闭channel</h3><p>关闭channel，表示：以后不会再发送新数据了。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">close</span>(ch)</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestChannel4</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> <span class="built_in">close</span>(ch)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line">ch &lt;- i</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line"><span class="comment">// 遍历channel</span></span><br><span class="line"><span class="keyword">for</span> v := <span class="keyword">range</span> ch &#123;</span><br><span class="line">t.Log(v)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>for v := range ch</code> : 会一直读取channel，直到channel被关闭。</p></div><p>如果对已经关闭的<code>channel</code>进行读取和写入，会发生啥呢？</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestChannel5</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>, <span class="number">1</span>)</span><br><span class="line">ch &lt;- <span class="number">100</span></span><br><span class="line"><span class="built_in">close</span>(ch)</span><br><span class="line">v1, ok1 := &lt;-ch</span><br><span class="line"><span class="comment">// v1 = 100, ok1 = true</span></span><br><span class="line">t.Logf(<span class="string">&quot;v1 = %d, ok1 = %v&quot;</span>, v1, ok1)</span><br><span class="line">v2, ok2 := &lt;-ch</span><br><span class="line"><span class="comment">// v2 = 0, ok2 = false</span></span><br><span class="line">t.Logf(<span class="string">&quot;v2 = %d, ok2 = %v&quot;</span>, v2, ok2)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// panic: send on closed channel [recovered, repanicked]</span></span><br><span class="line"><span class="comment">// ch &lt;- 200</span></span><br><span class="line"><span class="comment">// t.Log(ch)</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// panic: close of closed channel [recovered, repanicked]</span></span><br><span class="line"><span class="comment">// close(ch)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>读取已关闭的<code>channel</code>，代码不会报错，读取完后返回值为channel类型的<code>零值</code>，状态码为<code>false</code>，表示<code>channel</code>已关闭并且没有剩余数据。</p><ul><li>如果channel里还有值，先取值，然后ok &#x3D;&#x3D; true</li><li>如果channel已关闭且没有剩余值，则返回零值，ok &#x3D;&#x3D; false</li></ul></li><li><p>写入已关闭的<code>channel</code>，会<code>panic</code>。</p></li><li><p>关闭已关闭的<code>channel</code>。会<code>panic</code>。</p></li></ul><div class="note info simple"><p>所以Go官方也是明确提醒，向已关闭的<code>channel</code>发送数据会<code>panic</code>，因此通常需要确保所有发送都完成后再关闭<code>channel</code>。</p></div><p>那么，到底谁来关闭<code>channel</code>呢？</p><div class="note info simple"><p>日常经验：通常有发送方关闭channel，不由接收方关闭。</p></div><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 单向 Channel</span></span><br><span class="line"><span class="comment">// chan &lt;- int 只发送</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">producer</span><span class="params">(ch <span class="keyword">chan</span>&lt;- <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> <span class="built_in">close</span>(ch)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">1</span>; i &lt;= <span class="number">3</span>; i++ &#123;</span><br><span class="line">ch &lt;- i</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// &lt;- chan int 只接收</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">consumer</span><span class="params">(ch &lt;-<span class="keyword">chan</span> <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> v := <span class="keyword">range</span> ch &#123;</span><br><span class="line">fmt.Println(v)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestChannel6</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"><span class="keyword">go</span> producer(ch)</span><br><span class="line">consumer(ch)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式挺有有，可以让函数签名更清晰，减少误用。</p><h2 id="select"><a href="#select" class="headerlink" title="select"></a>select</h2><div class="note info simple"><p><code>select</code>类似<code>Java</code>里同时监听多个阻塞队列，但Go语法更直接</p></div><h3 id="基础用法"><a href="#基础用法" class="headerlink" title="基础用法"></a>基础用法</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSelect</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch1 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">ch2 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">time.Sleep(<span class="number">1</span> * time.Second)</span><br><span class="line">ch1 &lt;- <span class="string">&quot;from ch1&quot;</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">time.Sleep(<span class="number">2</span> * time.Second)</span><br><span class="line">ch2 &lt;- <span class="string">&quot;from ch2&quot;</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> msg := &lt;-ch1:</span><br><span class="line">t.Log(<span class="string">&quot;received&quot;</span>, msg)</span><br><span class="line"><span class="keyword">case</span> msg := &lt;-ch2:</span><br><span class="line">t.Log(<span class="string">&quot;received&quot;</span>, msg)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>哪个<code>channel</code>先准备好，就执行哪个<code>case</code>。看着就像是<code>switch</code>。</p><h3 id="select-timeout"><a href="#select-timeout" class="headerlink" title="select + timeout"></a>select + timeout</h3><p>这是日常开发的高频场景</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSelectTimeout</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">time.Sleep(<span class="number">2</span> * time.Second)</span><br><span class="line">ch &lt;- <span class="string">&quot;result&quot;</span></span><br><span class="line">&#125;()</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> msg := &lt;-ch:</span><br><span class="line">t.Log(<span class="string">&quot;received&quot;</span>, msg)</span><br><span class="line"><span class="keyword">case</span> &lt;-time.After(<span class="number">1</span> * time.Second):</span><br><span class="line">t.Log(<span class="string">&quot;timeout&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以类比于Java中的</p><ul><li><code>Future.get(timeout,unit)</code></li><li>一些异步框架里的超市等待</li></ul><h3 id="select-default-非阻塞操作"><a href="#select-default-非阻塞操作" class="headerlink" title="select + default 非阻塞操作"></a>select + default 非阻塞操作</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSelectDefault</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> v := &lt;-ch:</span><br><span class="line">fmt.Println(v)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;default&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>如果没有任何case就绪</li><li>立即执行default</li><li>不阻塞（如果没有<code>default</code>，在<code>ch</code>没有数据时，会阻塞等待）</li></ul><p>如果<code>channel</code>没有准备好，就执行<code>default</code>。注意：<code>default</code>用不好会造成忙轮询，例如下面这个错误的例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> v:= &lt;- ch:</span><br><span class="line">        fmt.Println(v)</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="comment">// 空转，占用CPU</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="退出信号"><a href="#退出信号" class="headerlink" title="退出信号"></a>退出信号</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> msg := &lt;- ch:</span><br><span class="line">        fmt.Println(<span class="string">&quot;msg &quot;</span>,msg)</span><br><span class="line">    <span class="keyword">case</span> &lt;- done:</span><br><span class="line">        fmt.Println(<span class="string">&quot;exit&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式通常用于：</p><ul><li>worker停止</li><li>后台协程退出</li><li>优雅关闭</li></ul><div class="note info simple"><p>如果多个case同时可执行的时候，Go会随机选择一个执行，它并不是书写顺序优先，不依赖顺序。</p></div><h3 id="空select"><a href="#空select" class="headerlink" title="空select"></a>空select</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>这种一般会永久阻塞，没啥意义。</p><h2 id="context"><a href="#context" class="headerlink" title="context"></a>context</h2><div class="note info simple"><p><code>context.Context</code> 用于在API边界之间传递取消信号、超时时间、deadline和请求级别的值，服务端收到请求后应创建<code>Context</code>，向下游调用时继续传递<code>Context</code></p></div><p>对于<code>Java</code>来说，大概可以这么理解：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">context = CancellationToken + time + RequestScope</span><br></pre></td></tr></table></figure><h3 id="用context取消goroutine"><a href="#用context取消goroutine" class="headerlink" title="用context取消goroutine"></a>用context取消goroutine</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">worker</span><span class="params">(ctx context.Context)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">fmt.Println(<span class="string">&quot;worker stopped:&quot;</span>, ctx.Err())</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;worker running...&quot;</span>)</span><br><span class="line">time.Sleep(<span class="number">500</span> * time.Millisecond)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestContext</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ctx, cancel := context.WithCancel(context.Background())</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> worker(ctx)</span><br><span class="line"></span><br><span class="line">time.Sleep(<span class="number">2</span> * time.Second)</span><br><span class="line">cancel()</span><br><span class="line"></span><br><span class="line">time.Sleep(<span class="number">1</span> * time.Second)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>打印如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestContext</span><br><span class="line">worker running...</span><br><span class="line">worker running...</span><br><span class="line">worker running...</span><br><span class="line">worker running...</span><br><span class="line">worker stopped: context canceled</span><br><span class="line">--- PASS: TestContext (3.00s)</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure><h3 id="context-WithTimeout"><a href="#context-WithTimeout" class="headerlink" title="context.WithTimeout"></a>context.WithTimeout</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">query</span><span class="params">(ctx context.Context)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-time.After(<span class="number">2</span> * time.Second):</span><br><span class="line">fmt.Println(<span class="string">&quot;query done&quot;</span>)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line"><span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line"><span class="keyword">return</span> ctx.Err()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestContextWithTimeout</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ctx, cancel := context.WithTimeout(context.Background(), <span class="number">1</span>*time.Second)</span><br><span class="line"><span class="keyword">defer</span> cancel()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := query(ctx); err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;query failed:&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// query failed: context deadline exceeded</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>日常开发里，HTTP 请求、数据库查询、RPC 调用都应该优先支持 context。</p><h2 id="Mutex"><a href="#Mutex" class="headerlink" title="Mutex"></a>Mutex</h2><p><code>Mutex</code>实际上就是Go语言中的互斥锁。对于<code>Java</code>来说，它相当于<code>synchronized</code>和<code>ReentrantLock</code>的最基础互斥能力。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> mu sync.Mutex</span><br></pre></td></tr></table></figure><p>虽然Go鼓励使用<code>channel</code>通信，但也不能完全不用锁。<code>sync.Mutex</code>和<code>channel</code>都是重要的同步手段，具体使用哪个看场景：</p><ul><li>需要保护共享状态：优先<code>Mutex</code></li><li>需要传递任务&#x2F;结果&#x2F;事件： 优先<code>channel</code></li></ul><p>和学习<code>Java</code>的<code>synchronized</code>一样，先来一个没有锁的例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMutex</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">count := <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++ &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">count++</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line">wg.Wait()</span><br><span class="line"><span class="comment">// 多次运行，每次结果都不同.</span></span><br><span class="line">fmt.Println(<span class="string">&quot;count =&quot;</span>, count)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>出现这个原因其实和Java多线程一样，多个<code>goroutine</code>同时修改<code>count</code>，会产生<code>data race</code>（数据竞争）。</p><p>此时就可以使用锁<code>Mutex</code>来修复这个问题：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMutex1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"><span class="keyword">var</span> mu sync.Mutex</span><br><span class="line">count := <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++ &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">mu.Lock()</span><br><span class="line">count++</span><br><span class="line">mu.Unlock()</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line">wg.Wait()</span><br><span class="line"><span class="comment">// 1000</span></span><br><span class="line">fmt.Println(<span class="string">&quot;count =&quot;</span>, count)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，学习了<code>defer</code>那就有更安全的写法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> mu.Unlock()</span><br><span class="line">count++</span><br></pre></td></tr></table></figure><h3 id="defer释放锁"><a href="#defer释放锁" class="headerlink" title="defer释放锁"></a>defer释放锁</h3><p>常见写法</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line"></span><br><span class="line"><span class="keyword">defer</span> mu.Unlock()</span><br></pre></td></tr></table></figure><p>优点：</p><ul><li>避免中途return，导致忘记解锁</li></ul><p>缺点：</p><ul><li>在极高频短路径里，<code>defer</code>有一点点开销</li><li>但绝大多数业务代码优先考虑正确性和可维护性</li></ul><h3 id="RWmutex"><a href="#RWmutex" class="headerlink" title="RWmutex"></a>RWmutex</h3><p>语法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> mu sync.RWMutex</span><br></pre></td></tr></table></figure><ul><li>RLock(): 读锁</li><li>Lock() 写锁</li></ul><p>这种类型的锁非常适合<code>读多写少</code>，除此之外，使用<code>Mutex</code>即可。</p><p>对于锁，各大语言的坑应该差不多，需要特别注意以下几点：</p><ol><li>忘记解锁：会导致死锁或者后续<code>goroutine</code>永久阻塞。</li><li>重复加锁顺序不一致：多个锁嵌套时，如果不同<code>goroutine</code>获取锁顺序不一致，容易死锁。</li><li>锁住范围过大</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line">doSometingSlow()</span><br><span class="line">mu.Unlock()</span><br></pre></td></tr></table></figure><p>如果临界区里包含慢操作，会严重影响并发性能，应该尽量：</p><ul><li>缩小加锁范围</li><li>只保护共享数据本身</li></ul><h2 id="Once"><a href="#Once" class="headerlink" title="Once"></a>Once</h2><p><code>sync.Once</code>用来保证某段代码只执行一次。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> once sync.Once</span><br></pre></td></tr></table></figure><p>下面是一个示例：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestOnce</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> once sync.Once</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line">once.Do(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;hello once&quot;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>无论多少 goroutine 调用，里面那段逻辑都只会执行一次。从这个名字就能看出来，应该可以用在<code>单例模式</code>，除此之外还支持如下场景：</p><ul><li>配置加载一次</li><li>连接池初始化</li><li>懒加载共享资源</li></ul><p>使用<code>once.Do</code>需要注意的是，一旦<code>Do</code>里面的函数<code>panic</code>，也算调用过，后续不会再重试。所以有那种可能出现失败重试的逻辑，不要使用这个特性。</p><h2 id="Goroutine泄露"><a href="#Goroutine泄露" class="headerlink" title="Goroutine泄露"></a>Goroutine泄露</h2><div class="note info simple"><p>一个<code>goroutine</code>被永久阻塞，无法退出，这个就叫<code>goroutine leak</code>。</p></div><p>例如下面这个例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// fatal error: all goroutines are asleep - deadlock!</span></span><br><span class="line"><span class="comment">// goroutine 1 [chan receive]:</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutineLeak</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="comment">// 无人接收，永远阻塞</span></span><br><span class="line">ch &lt;- <span class="number">1</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时，可以利用<code>context</code>来解决：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">send</span><span class="params">(ctx context.Context, ch <span class="keyword">chan</span>&lt;- <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> ch &lt;- <span class="number">1</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;sent...&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">fmt.Println(<span class="string">&quot;cancelled...&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutineLeakContext</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ctx, cancel := context.WithTimeout(context.Background(), <span class="number">1</span>*time.Second)</span><br><span class="line"><span class="keyword">defer</span> cancel()</span><br><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"><span class="keyword">go</span> send(ctx, ch)</span><br><span class="line">time.Sleep(<span class="number">2</span> * time.Second)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>即使没人接收，<code>goroutine</code>也会因为<code>context</code>超市而退出。</p><h2 id="Worker-Pool"><a href="#Worker-Pool" class="headerlink" title="Worker Pool"></a>Worker Pool</h2><p>学习并发，那就一定会涉及到这个问题，对于Java来说，可以使用<code>ThreadPool</code>来处理，对于Go，也可以实现类似的效果：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">workerPool</span><span class="params">(id <span class="type">int</span>, jobs &lt;-<span class="keyword">chan</span> <span class="type">int</span>, wg *sync.WaitGroup)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> wg.Done()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> job := <span class="keyword">range</span> jobs &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;worker %d processing job %d\n&quot;</span>, id, job)</span><br><span class="line">time.Sleep(<span class="number">500</span> * time.Millisecond)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutineWorkerPool</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 最多3个worker同时处理任务</span></span><br><span class="line">    <span class="keyword">const</span> workerCount = <span class="number">3</span></span><br><span class="line"><span class="keyword">const</span> jobCount = <span class="number">10</span></span><br><span class="line"></span><br><span class="line">jobs := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">1</span>; i &lt;= workerCount; i++ &#123;</span><br><span class="line">wg.Add(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">go</span> workerPool(i, jobs, &amp;wg)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> j := <span class="number">1</span>; j &lt;= jobCount; j++ &#123;</span><br><span class="line">jobs &lt;- j</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">close</span>(jobs)</span><br><span class="line">wg.Wait()</span><br><span class="line">fmt.Println(<span class="string">&quot;all jobs done&quot;</span>)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="panic在goroutine中不会被其他goroutine-recover"><a href="#panic在goroutine中不会被其他goroutine-recover" class="headerlink" title="panic在goroutine中不会被其他goroutine recover"></a>panic在goroutine中不会被其他goroutine recover</h2><p>下面看个例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGoroutinePanic</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> r := <span class="built_in">recover</span>(); r != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;recover:&quot;</span>, r)</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">&quot;panic&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的<code>recover</code>捕获不到另一个<code>goroutine</code>里面的<code>panic</code>。</p><div class="note info simple"><p><code>recover</code>只能捕获同一个<code>goroutine</code>调用栈上的<code>panic</code>，不能跨<code>goroutine</code>捕获。</p></div>]]>
    </content>
    <id>https://dev.net.cn/learning-go-06/</id>
    <link href="https://dev.net.cn/learning-go-06/"/>
    <published>2026-04-05T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天开始学习Go语言的重要特性，<code>Goroutine</code>。</p>
<h2 id="goroutine"><a href="#goroutine" class="headerlink" title="goroutine"></a>goroutine</h]]>
    </summary>
    <title>Go语言学习笔记（六）- 并发编程</title>
    <updated>2026-04-05T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<h2 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h2><p>在Go语言中，接口的设计是一个典型的“鸭子类型”(Duck Typing)。<strong>如果一个结构体拥有了某个接口定义的所有方法，那么它就实现了这个接口。</strong></p><p>先看看Java的接口（显示实现接口）</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Animal</span>&#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">say</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span> <span class="keyword">implements</span> <span class="title class_">Animal</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">say</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;wang!&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>类必须通过<code>implements</code>关键字明确申明自己实现了某个接口。</p><p>再看看Go的接口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Animal <span class="keyword">interface</span> &#123;</span><br><span class="line">    Say();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Dog <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(d Dog)</span></span> Say()&#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;wang!&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Go是隐式实现接口，只要一个类型实现了接口需求的所有方法，就自动认为他实现了该接口。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">MakeSound</span><span class="params">(a Animal)</span></span> &#123;</span><br><span class="line">a.Say()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestInterface1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">d := Dog&#123;&#125;</span><br><span class="line">MakeSound(d)</span><br><span class="line">    <span class="comment">// 另一种使用方法</span></span><br><span class="line"><span class="keyword">var</span> a Animal</span><br><span class="line"><span class="comment">// Dog 实现了（通俗的说长得像） Animal 接口，所以可以赋值给 a</span></span><br><span class="line">a = Dog&#123;&#125;</span><br><span class="line">a.Say()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>Go 的接口更像是：只关心你能做什么，不关心你是谁。</p></div><p>任何类型，只要有<code>Say()</code>方法，都可以当成<code>Animal</code>使用。</p><p>这一块和Java差距过大（和TypeScript几乎一致），还需要一段时间适应。当时学习TS的适合，就很容易带入Java的思维，先定义接口，再写实现。对于这类语言，似乎应该先实现，然后有需要抽象的时候再提取为接口。</p><p>对于接口，主要知识点在于：</p><ol><li>Go是隐式实现接口</li><li>Go类型只要拥有接口申明的所有方法，就自动实现该接口</li><li>Go的接口实现是结构化类型系统的一种体现</li><li>Go更强调行为抽象，而不是继承关系。</li></ol><h2 id="空接口"><a href="#空接口" class="headerlink" title="空接口"></a>空接口</h2><p>空接口是没有任何方法的接口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>因为它不要求任何方法，所以所有类型都实现了空接口。(Java的Object？在Go中它也叫any)</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestEmptyInterface</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 老古董语法了，推荐使用any别名</span></span><br><span class="line"><span class="keyword">var</span> x <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">x = <span class="number">10</span></span><br><span class="line">x = <span class="string">&quot;Hello&quot;</span></span><br><span class="line">x = <span class="number">3.1354</span></span><br><span class="line">x = <span class="literal">true</span></span><br><span class="line">x = <span class="literal">nil</span></span><br><span class="line">x = []<span class="type">int</span>&#123;<span class="number">123</span>, <span class="number">456</span>, <span class="number">789</span>&#125;</span><br><span class="line">t.Log(x)</span><br><span class="line"><span class="comment">// Go 1.18开始，引入了 any 作为 interface&#123;&#125; 的别名，any 更加简洁易读。</span></span><br><span class="line"><span class="keyword">var</span> y any</span><br><span class="line">y = <span class="number">10</span></span><br><span class="line">y = <span class="string">&quot;Hello&quot;</span></span><br><span class="line">y = <span class="number">3.1354</span></span><br><span class="line">y = <span class="literal">true</span></span><br><span class="line">y = <span class="literal">nil</span></span><br><span class="line">y = []<span class="type">int</span>&#123;<span class="number">123</span>, <span class="number">456</span>, <span class="number">789</span>&#125;</span><br><span class="line">t.Log(y)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那么这玩意有啥用呢？相当于通用参数，对于Java程序员，下面的例子就很直观了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">say</span><span class="params">(Object obj)</span>&#123;</span><br><span class="line">    System.out.println(obj.toString())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应到Go</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrintValue</span><span class="params">(v <span class="keyword">interface</span>&#123;&#125;)</span></span>&#123;</span><br><span class="line">    fmt.Println(v)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 或者更常用的</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrintValueAny</span><span class="params">(v any)</span></span>&#123;</span><br><span class="line">    fmt.Println(v)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>any</code>，也就是<code>空接口</code>与<code>泛型</code>有啥区别呢？</p><ul><li><p><code>interface&#123;&#125;</code> 是运行时动态类型</p></li><li><p>泛型是编译期类型参数</p></li><li><p><code>interface&#123;&#125;</code> 使用时通常需要类型断言</p></li><li><p>泛型可以保留类型信息</p></li></ul><h2 id="类型断言"><a href="#类型断言" class="headerlink" title="类型断言"></a>类型断言</h2><p>类型断言用于从接口中取出具体类型。</p><h3 id="类型断言的语法"><a href="#类型断言的语法" class="headerlink" title="类型断言的语法"></a>类型断言的语法</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 推荐写法</span></span><br><span class="line">value,ok := x.(T)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者 偷懒写法</span></span><br><span class="line">value := x.(T)</span><br></pre></td></tr></table></figure><p>下面通过一个列子来说明</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTypeAssertion</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> x any = <span class="string">&quot;Hello&quot;</span></span><br><span class="line">s, ok := x.(<span class="type">string</span>)</span><br><span class="line"><span class="keyword">if</span> ok &#123;</span><br><span class="line">t.Log(s)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;not string&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果是偷懒写法（不安全），如果x的动态类型不是<code>string</code>，程序会<code>panic</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTypeAssertionUnSafe</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> x any = <span class="number">1234</span></span><br><span class="line"><span class="comment">// --- FAIL: TestTypeAssertionUnSafe (0.00s)</span></span><br><span class="line"><span class="comment">// panic: interface conversion: interface &#123;&#125; is int, not string [recovered, repanicked]</span></span><br><span class="line">s := x.(<span class="type">string</span>)</span><br><span class="line">t.Log(s)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以，推荐使用第一种写法，更安全，更可靠。</p><h2 id="类型-switch"><a href="#类型-switch" class="headerlink" title="类型 switch"></a>类型 switch</h2><p>当一个接口可能有多种类型时，可以用 <code>type switch</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrintType</span><span class="params">(v any)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> value := v.(<span class="keyword">type</span>) &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="type">string</span>:</span><br><span class="line">fmt.Printf(<span class="string">&quot;string: %v\n&quot;</span>, value)</span><br><span class="line"><span class="keyword">case</span> <span class="type">int</span>:</span><br><span class="line">fmt.Printf(<span class="string">&quot;int: %v\n&quot;</span>, value)</span><br><span class="line"><span class="keyword">case</span> <span class="type">bool</span>:</span><br><span class="line">fmt.Printf(<span class="string">&quot;bool: %v\n&quot;</span>, value)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Printf(<span class="string">&quot;other: %T\n&quot;</span>, value)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Class <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTypeSwitch</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">PrintType(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">PrintType(<span class="literal">true</span>)</span><br><span class="line">PrintType(<span class="number">123</span>)</span><br><span class="line"><span class="comment">// other: day5.Class</span></span><br><span class="line">PrintType(Class&#123;Name: <span class="string">&quot;Java&quot;</span>&#125;)</span><br><span class="line"><span class="comment">//other: float64</span></span><br><span class="line">PrintType(<span class="number">3.45</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在第四章的switch介绍时，顺便也加入了这个特性，这里就再啰嗦一下，毕竟既是编码常用，也是面试重点：</p><div class="note info simple"><p><code>v.(type)</code> 只能用于 <code>switch</code> 中，不能单独使用。</p></div><h2 id="结构体嵌入（组合）"><a href="#结构体嵌入（组合）" class="headerlink" title="结构体嵌入（组合）"></a>结构体嵌入（组合）</h2><div class="note info simple"><p>Go 没有传统继承</p></div><h3 id="普通组合"><a href="#普通组合" class="headerlink" title="普通组合"></a>普通组合</h3><p>对于Java来说，存在一个类与类之间的关系，包括继承、组合、聚合之类的关系。但是Go就只有组合。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">eat</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 继承关系</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span> <span class="keyword">extends</span> <span class="title class_">Animal</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>如果是Go：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Engine <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e Engine)</span></span> Start() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;start...&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Car <span class="keyword">struct</span> &#123;</span><br><span class="line">engine Engine</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestComposition</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">car := Car&#123;engine: Engine&#123;&#125;&#125;</span><br><span class="line">car.engine.Start()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述这个例子从称之为<code>普通组合</code>。</p><h3 id="结构体嵌入"><a href="#结构体嵌入" class="headerlink" title="结构体嵌入"></a>结构体嵌入</h3><p>Go 特有的一种组合方式，通常被称为 <strong>Struct Embedding</strong>（结构体嵌套&#x2F;内嵌）。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Engine <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e Engine)</span></span> Start() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;start...&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Tesla <span class="keyword">struct</span> &#123;</span><br><span class="line">Engine</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestComposition1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">car := Tesla&#123;Engine&#123;&#125;&#125;</span><br><span class="line">car.Start()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看起来像继承，但本质是组合。</p><p>上述例子可行的原因是：<strong>方法提升</strong></p><p>当结构体嵌入另一个结构体时，被嵌入类型的方法会被提升。现在再通过一个类似的例子说明：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Animal1 <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(a Animal1)</span></span> Eat() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;eat....&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Dog1 <span class="keyword">struct</span> &#123;</span><br><span class="line">Animal1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestComposition2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">d := Dog1&#123;&#125;</span><br><span class="line">d.Eat()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Dog1</code> 可以直接调用 <code>Eat()</code>，但不是继承。本质上相当于</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">d.Animal.Eat()</span><br></pre></td></tr></table></figure><p>当然对于组合来说，Java存在的问题，Go肯定也会存在。那就是如果两个结构体的字段名称和类型一模一样，那么该如何处理呢？</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> A <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> B <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> C <span class="keyword">struct</span> &#123;</span><br><span class="line">A</span><br><span class="line">B</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestComposition3</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">c := C&#123;&#125;</span><br><span class="line"><span class="comment">//不明确的引用 &#x27;Name&#x27;</span></span><br><span class="line"><span class="comment">//fmt.Println(c.Name)</span></span><br><span class="line">fmt.Println(c.A.Name)</span><br><span class="line">fmt.Println(c.B.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>遇到这种情况时，就必须明确指定，访问的是谁的<code>Name</code>。</p><p>这个特性应该会比较常用，例如<code>Java</code>中，会讲一些固定的字段抽取出来放在<code>Base</code>中，其他类只需要继承这个类就可以复用基础字段，对于Go也可以，但不是继承，而是组合</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> BaseModel <span class="keyword">struct</span> &#123;</span><br><span class="line">ID        <span class="type">int</span></span><br><span class="line">CreatedAt time.Time</span><br><span class="line">UpdatedAt time.Time</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> user <span class="keyword">struct</span> &#123;</span><br><span class="line">BaseModel</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestComposition4</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := user&#123;Name: <span class="string">&quot;Tom&quot;</span>, BaseModel: BaseModel&#123;</span><br><span class="line">ID:        <span class="number">1</span>,</span><br><span class="line">CreatedAt: time.Now(),</span><br><span class="line">UpdatedAt: time.Now(),</span><br><span class="line">&#125;&#125;</span><br><span class="line">fmt.Println(u.ID)</span><br><span class="line">fmt.Println(u.CreatedAt)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>Favor composition over inheritance (组合优于继承)</p></div><p>对于面试来说，只需要记住一点：</p><div class="note info simple"><p>Go没有传统继承，它使用结构体嵌入和接口实现来完成代码复用和多态。</p></div><h2 id="方法接收者-Receiver"><a href="#方法接收者-Receiver" class="headerlink" title="方法接收者(Receiver)"></a>方法接收者(Receiver)</h2><p>Go 没有 Java 中的类方法语法，但可以给类型定义方法。例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">    Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u User)</span></span> SayHello()&#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello&quot;</span>,u.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的<code>(u User)</code>就是方法接收者。类似的Java代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="built_in">this</span>.name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在Go语言中，方法接收者有两种：<code>值接收者</code>和<code>指针接收者</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 值接收者</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u User)</span></span> Method()</span><br><span class="line"><span class="comment">// 指针接收者</span></span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(u *User)</span></span> Method()</span><br></pre></td></tr></table></figure><h3 id="值接收者"><a href="#值接收者" class="headerlink" title="值接收者"></a>值接收者</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里会复制一个对象</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u User)</span></span> Rename(name <span class="type">string</span>) &#123;</span><br><span class="line">u.Name = name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加了 (u User/*User) 就是这个结构体的方法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u User)</span></span> Add(a, b <span class="type">int</span>) <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestReceiver</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := User&#123;Name: <span class="string">&quot;Tom&quot;</span>&#125;</span><br><span class="line">u.Rename(<span class="string">&quot;Jerry&quot;</span>)</span><br><span class="line"><span class="comment">// Tom</span></span><br><span class="line">t.Log(u.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里修改名字是不会生效的，因为对于值接收者，他是复制一个对象，然后再去修改。日常编码中，这种方式使用的频率很低。</p><h3 id="指针接受者"><a href="#指针接受者" class="headerlink" title="指针接受者"></a>指针接受者</h3><p>如果想要修改原对象里的某个值，那就需要传入指针，这也是日常编码中最常用的方式。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> RenamePointer(name <span class="type">string</span>) &#123;</span><br><span class="line">u.Name = name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加了 (u User/*User) 就是这个结构体的方法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u User)</span></span> Add(a, b <span class="type">int</span>) <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestReceiver</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := User&#123;Name: <span class="string">&quot;Tom&quot;</span>&#125;</span><br><span class="line">u.RenamePointer(<span class="string">&quot;Tony&quot;</span>)</span><br><span class="line">t.Log(u.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于Java程序员来说，稍微有些区别，需要特别注意这一点。</p><p>这两个特性其实在前期学习<code>slice</code>、<code>map</code>、<code>函数</code>等地方已经提前接触过了。</p><p>下面再温故而知新，复述一下，使用场景：</p><h3 id="使用指针接收者的场景"><a href="#使用指针接收者的场景" class="headerlink" title="使用指针接收者的场景"></a>使用指针接收者的场景</h3><h4 id="方法需要修改接收者"><a href="#方法需要修改接收者" class="headerlink" title="方法需要修改接收者"></a>方法需要修改接收者</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 例如</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> RenamePointer(name <span class="type">string</span>) &#123;</span><br><span class="line">u.Name = name</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="结构体较大，避免复制成本"><a href="#结构体较大，避免复制成本" class="headerlink" title="结构体较大，避免复制成本"></a>结构体较大，避免复制成本</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> BigStruct <span class="keyword">struct</span> &#123;</span><br><span class="line">    Data [<span class="number">1024</span>]<span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *BigStruct)</span></span> Process()&#123;&#125;</span><br></pre></td></tr></table></figure><h4 id="保持方法集一致"><a href="#保持方法集一致" class="headerlink" title="保持方法集一致"></a>保持方法集一致</h4><p>如果一个类型有些方法用了指针接收者，通常建议其他方法也是用指针接收者。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> SetName(name <span class="type">string</span>)&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> GetName() <span class="type">string</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>这样可以避免接口实现时出现混乱。</p><h3 id="使用值接收者的场景"><a href="#使用值接收者的场景" class="headerlink" title="使用值接收者的场景"></a>使用值接收者的场景</h3><h4 id="小对象且不可变语义"><a href="#小对象且不可变语义" class="headerlink" title="小对象且不可变语义"></a>小对象且不可变语义</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Point <span class="keyword">struct</span> &#123;</span><br><span class="line">    X <span class="type">int</span></span><br><span class="line">    Y <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Point)</span></span> Distance() <span class="type">float64</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> math.Sqrt(<span class="type">float64</span>(p.X*p.X + p.Y * p.Y))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="基础类型别名"><a href="#基础类型别名" class="headerlink" title="基础类型别名"></a>基础类型别名</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> MyInt <span class="type">int</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m MyInt)</span></span> IsPositive() <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> m &gt; <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="不希望方法修改原始对象"><a href="#不希望方法修改原始对象" class="headerlink" title="不希望方法修改原始对象"></a>不希望方法修改原始对象</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u User)</span></span> Display()&#123;</span><br><span class="line">    fmt.Println(u.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="方法调用时得自动取址"><a href="#方法调用时得自动取址" class="headerlink" title="方法调用时得自动取址"></a>方法调用时得自动取址</h3><p>Go语言有一个语法糖：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">user := User&#123;&#125;</span><br><span class="line">user.SetName(<span class="string">&quot;Tom&quot;</span>)</span><br></pre></td></tr></table></figure><p>如果<code>SetName</code>是指针接收者</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> SetName(name <span class="type">string</span>)&#123;&#125;</span><br></pre></td></tr></table></figure><p>Go语言会自动转换为</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(&amp;user).SetName(<span class="string">&quot;Tom&quot;</span>)</span><br></pre></td></tr></table></figure><p>但是，这只在变量可寻址时有效。</p><p>不可寻址得情况：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 无法在 &#x27;User&#123;&#125;&#x27; 中调用指针方法</span></span><br><span class="line"><span class="comment">// User&#123;&#125;.SetName(&quot;Tom&quot;)</span></span><br></pre></td></tr></table></figure><p>如果<code>SetName</code>是市政接收者，这通常不允许，因为临时值不可获取地址。</p><h3 id="方法集与接口实现"><a href="#方法集与接口实现" class="headerlink" title="方法集与接口实现"></a>方法集与接口实现</h3><p>换一种说法就是： <code>T</code> <strong>和</strong> <code>*T</code> 的方法集。</p><p>对于值接受者：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Speaker <span class="keyword">interface</span> &#123;</span><br><span class="line">Speak()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Cat <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(cat Cat)</span></span> Speak() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;wangwang&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestInterface</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> s Speaker</span><br><span class="line">cat := Cat&#123;&#125;</span><br><span class="line"><span class="comment">// 这样可以</span></span><br><span class="line">s = cat</span><br><span class="line">s.Speak()</span><br><span class="line"><span class="comment">// 这样也可以</span></span><br><span class="line">s = &amp;cat</span><br><span class="line">s.Speak()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为值接收者方法属于：</p><ul><li><code>Cat</code>的方法集</li><li><code>*Cat</code>的方法集</li></ul><p>对于指针接收者：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Bird <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(bird *Bird)</span></span> Speak() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;jijizhazha&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestInterface2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> s Speaker</span><br><span class="line">bird := Bird&#123;&#125;</span><br><span class="line"><span class="comment">// 这样可以</span></span><br><span class="line">s = &amp;bird</span><br><span class="line">s.Speak()</span><br><span class="line"><span class="comment">// 这样不可以</span></span><br><span class="line"><span class="comment">//无法将 bird (类型 Bird) 用作类型 Speaker</span></span><br><span class="line"><span class="comment">//类型未实现 Speaker，因为 Speak 方法有指针接收器</span></span><br><span class="line"><span class="comment">//s = bird</span></span><br><span class="line"><span class="comment">//s.Speak()</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为指针接收者方法只属于<code>*Bird</code>的方法集，不属于<code>Bird</code>的方法集。</p><p>总结下，<code>值接收者</code>和<code>指针接受者</code>的区别：</p><ul><li>值接收者会复制接收者</li><li>指针接收者传递地址</li><li>指针接收者可以修改原来的对象</li><li>大结构体通常用指针接收者，避免复制</li><li>方法集不同，会影响接口实现</li></ul><h2 id="综合案例"><a href="#综合案例" class="headerlink" title="综合案例"></a>综合案例</h2><p>下面通过一个综合案例，将上面的知识点都串联起来。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> day5</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;errors&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserInfo <span class="keyword">struct</span> &#123;</span><br><span class="line">ID   <span class="type">int64</span></span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 知识点1：接口定义</span></span><br><span class="line"><span class="keyword">type</span> UserRepository <span class="keyword">interface</span> &#123;</span><br><span class="line">FindByID(id <span class="type">int64</span>) (*UserInfo, <span class="type">error</span>)</span><br><span class="line">Save(user *UserInfo) <span class="type">error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> MemoryUserRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">data <span class="keyword">map</span>[<span class="type">int64</span>]*UserInfo</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewMemoryUserRepository</span><span class="params">()</span></span> *MemoryUserRepository &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;MemoryUserRepository&#123;</span><br><span class="line">data: <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">int64</span>]*UserInfo),</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 知识点2： 隐式实现</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *MemoryUserRepository)</span></span> FindByID(id <span class="type">int64</span>) (*UserInfo, <span class="type">error</span>) &#123;</span><br><span class="line">user, ok := r.data[id]</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, errors.New(<span class="string">&quot;not found&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 知识点2： 隐式实现，知识点3：指针接收者</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *MemoryUserRepository)</span></span> Save(user *UserInfo) <span class="type">error</span> &#123;</span><br><span class="line">r.data[user.ID] = user</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Logger <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(log Logger)</span></span> Info(message <span class="type">string</span>) &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[INFO]&quot;</span>, message)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 知识点4：结构体嵌入，知识点5组合复用</span></span><br><span class="line"><span class="keyword">type</span> UserService <span class="keyword">struct</span> &#123;</span><br><span class="line">repo UserRepository</span><br><span class="line">Logger</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewUserService</span><span class="params">(repo UserRepository)</span></span> *UserService &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserService&#123;</span><br><span class="line">repo: repo,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *UserService)</span></span> CreateUser(id <span class="type">int64</span>, name <span class="type">string</span>) <span class="type">error</span> &#123;</span><br><span class="line">s.Info(<span class="string">&quot;Creating user...&quot;</span>)</span><br><span class="line">user := &amp;UserInfo&#123;</span><br><span class="line">ID:   id,</span><br><span class="line">Name: name,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> s.repo.Save(user)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *UserService)</span></span> GetUser(id <span class="type">int64</span>) (*UserInfo, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> s.repo.FindByID(id)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestDay5</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">repo := NewMemoryUserRepository()</span><br><span class="line">service := NewUserService(repo)</span><br><span class="line"></span><br><span class="line">err := service.CreateUser(<span class="number">1</span>, <span class="string">&quot;John&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">user, err := service.GetUser(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(user.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-go-05/</id>
    <link href="https://dev.net.cn/learning-go-05/"/>
    <published>2026-04-04T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h2><p>在Go语言中，接口的设计是一个典型的“鸭子类型”(Duck Typing)。<strong>如果一个结构体拥有了某个接口定义的所有方法，那么]]>
    </summary>
    <title>Go语言学习笔记（五）- 接口与类型系统</title>
    <updated>2026-04-04T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<p>今天简单的学习下流程控制与函数，这一块大多数语言都差不多。</p><h2 id="if-else"><a href="#if-else" class="headerlink" title="if&#x2F;else"></a>if&#x2F;else</h2><p>Go语言的<code>if</code>不需要<code>小括号</code>，但代码块必须使用<code>&#123;&#125;</code>包裹。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIf</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">age := <span class="number">30</span></span><br><span class="line"><span class="keyword">if</span> age &gt;= <span class="number">18</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;adult&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;teenager&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其次，if条件必须是bool，不支持将整数、字符串、指针等作为条件。</p><div class="note info simple"><p>Go 不像 C &#x2F; C++ &#x2F; JavaScript，不能用 <code>0</code>、<code>1</code>、<code>nil</code> 等隐式转 bool。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIf1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">n := <span class="number">1</span></span><br><span class="line"><span class="comment">//非布尔值 &#x27;n&#x27; (类型 int) 用作条件</span></span><br><span class="line"><span class="keyword">if</span> n &#123;</span><br><span class="line">t.Log(<span class="string">&quot;error&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="if支持初始化语句"><a href="#if支持初始化语句" class="headerlink" title="if支持初始化语句"></a>if支持初始化语句</h3><p>这是 Go 很常见的写法，尤其用于错误处理、map 查询、类型转换等场景。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> err := doSomething(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Log(err)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">readConfig</span><span class="params">()</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := loadFile(); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">loadFile</span><span class="params">()</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="type">error</span>(<span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>初始化语句中的变量，只有在<code>if</code>&#x2F;<code>else if</code>&#x2F; <code>else</code>结构中可见。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIfScope</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> x := <span class="number">15</span>; x &gt; <span class="number">5</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;x is greater than 5&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(x)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>常见用法：<code>错误处理</code>、<code>map查询</code>、<code>类型断言</code>，例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIfMap</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line"><span class="string">&quot;index&quot;</span>: <span class="number">1</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> value, ok := m[<span class="string">&quot;index&quot;</span>]; ok &#123;</span><br><span class="line">t.Log(value)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;not found&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//或者错误处理</span></span><br><span class="line"><span class="keyword">if</span> err:=saveUser(user); err !=<span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除了必须有<code>&#123;&#125;</code>，还有一个必须那就是else必须紧跟着if的右花括号。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIfElse</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">n := <span class="number">20</span></span><br><span class="line"><span class="keyword">if</span> n &gt;=<span class="number">18</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;adult&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 这里会报错，Go对换行很敏感，因为编译器会自动插入分号。</span></span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;teenager&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 需要紧跟着if的右边花括号。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestIfElse</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">n := <span class="number">20</span></span><br><span class="line"><span class="keyword">if</span> n &gt;=<span class="number">18</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;adult&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;teenager&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="else-if"><a href="#else-if" class="headerlink" title="else if"></a>else if</h3><p>没啥好说的，好<code>java</code>一样，其他的遵循go的规则，主要是没有小括号，必须花括号，并且紧跟着上一个的右边花括号。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestElseIf</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="number">80</span></span><br><span class="line"><span class="keyword">if</span> s &gt;= <span class="number">90</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;A&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> s &gt;= <span class="number">80</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;B&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> s &gt;= <span class="number">60</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;C&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编码时，可以用<code>if</code>实现<code>Guard Clause（守卫语句）</code>，在方法的开头先检查<code>不满足继续执行条件</code>的情况，遇到异常、非法参数、边界条件就提前返回、抛异常、跳过。避免后面的主逻辑被多层<code>if</code>包裹起来。下面的例子做个对比。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// guard clause写法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Process</span><span class="params">()</span></span> <span class="type">error</span> &#123;</span><br><span class="line"> data, err := LoadData()</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> err</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(data) == <span class="number">0</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;empty data&quot;</span>)</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> SaveData(data)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 嵌套写法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Process</span><span class="params">()</span></span> <span class="type">error</span> &#123;</span><br><span class="line"> data, err := LoadData()</span><br><span class="line"> <span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line">     <span class="keyword">if</span> <span class="built_in">len</span>(data) != <span class="number">0</span> &#123;</span><br><span class="line">         <span class="keyword">return</span> SaveData(data)</span><br><span class="line">     &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">         <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;empty data&quot;</span>)</span><br><span class="line">     &#125;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">     <span class="keyword">return</span> err</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="switch"><a href="#switch" class="headerlink" title="switch"></a>switch</h2><p>对于switch，和<code>Java</code>区别也不大。只是不需要写<code>break</code>，因为默认不会向下执行(<code>fallthrough</code>)。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSwitch</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">day := <span class="string">&quot;Monday&quot;</span></span><br><span class="line"><span class="keyword">switch</span> day &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Monday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Monday&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Tuesday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Tuesday&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Wednesday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Wednesday&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Thursday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Thursday&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Friday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Friday&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，case里支持多个条件：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> day &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Monday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;work day&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;Saturday&quot;</span>, <span class="string">&quot;Sunday&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;weekend&quot;</span>)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">t.Log(<span class="string">&quot;normal&quot;</span>)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果想继续执行下一个case，可以使用<code>fallthrough</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestFallThrough</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">n := <span class="number">1</span></span><br><span class="line"><span class="keyword">switch</span> n &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">t.Log(<span class="string">&quot;1&quot;</span>)</span><br><span class="line"><span class="keyword">fallthrough</span></span><br><span class="line"><span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">t.Log(<span class="string">&quot;2&quot;</span>)</span><br><span class="line"><span class="keyword">fallthrough</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">t.Log(<span class="string">&quot;default&quot;</span>)</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>fallthrough</code> 只会进入下一个 case，不会重新判断下一个 case 的条件。</p></div><p><code>switch</code>和<code>if</code>一样，都支持初始化语句</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSwitchInit</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> os := runtime.GOOS; os &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;darwin&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;macOS&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;linux&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Linux&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;windows&quot;</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Windows&quot;</span>)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">t.Log(<span class="string">&quot;Other OS&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和<code>if</code>一样的，这里的<code>os</code>也只有在<code>switch</code>内部可见。对于上面<code>if</code>的判断<code>TestElseIf()</code>，也可以修改为<code>switch</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSwitch1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="number">80</span></span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> s &gt;= <span class="number">90</span>:</span><br><span class="line">t.Log(<span class="string">&quot;A&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> s &gt;= <span class="number">80</span>:</span><br><span class="line">t.Log(<span class="string">&quot;B&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> s &gt;= <span class="number">60</span>:</span><br><span class="line">t.Log(<span class="string">&quot;C&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里可以看到，switch后面没有更任何表达式，这就相当于switch true，通常用来替代复杂的<code>if/else if</code>语句。</p><p>switch的case表达式，还可以是任意可比较的表达式</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSwitch2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">10</span></span><br><span class="line"><span class="keyword">switch</span> x &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="number">1</span> + <span class="number">1</span>:</span><br><span class="line">t.Log(<span class="string">&quot;2&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="number">5</span> * <span class="number">2</span>:</span><br><span class="line">t.Log(<span class="number">10</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>switch的case是从上到下匹配，只要有一个条件匹配成功，后面的就不会再执行。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSwitch3</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="number">80</span></span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> s &gt;= <span class="number">60</span>:</span><br><span class="line">t.Log(<span class="string">&quot;C&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> s &gt;= <span class="number">80</span>:</span><br><span class="line">t.Log(<span class="string">&quot;B&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> s &gt;= <span class="number">90</span>:</span><br><span class="line">t.Log(<span class="string">&quot;A&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时，只会输出<code>c</code>,，因为第一个已经匹配成功，后面的就不会执行了。编写代码时，一定要注意<code>case</code>的条件，要把更具体的条件放在最前面。</p><h3 id="type-switch-类型选择"><a href="#type-switch-类型选择" class="headerlink" title="type switch 类型选择"></a>type switch 类型选择</h3><p>这是比较重要的一点，主要用于判断接口变量的动态类型：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrintType</span><span class="params">(v any)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> value := v.(<span class="keyword">type</span>) &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="type">int</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;int: &quot;</span>, value)</span><br><span class="line"><span class="keyword">case</span> <span class="type">float64</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;float64: &quot;</span>, value)</span><br><span class="line"><span class="keyword">case</span> <span class="type">string</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;string: &quot;</span>, value)</span><br><span class="line"><span class="keyword">case</span> <span class="type">bool</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;bool: &quot;</span>, value)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;unknown&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSwitchType</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">PrintType(<span class="number">123</span>)</span><br><span class="line">PrintType(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">PrintType(<span class="number">3.14</span>)</span><br><span class="line">PrintType(<span class="literal">true</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>v.(type)</code> 只能用于 <code>switch</code> 中，不能单独使用。</p></div><div class="note info simple"><p>在每个 <code>case</code> 中，变量 <code>x</code> 的静态类型会变成对应 case 的类型。</p></div><h2 id="for-循环"><a href="#for-循环" class="headerlink" title="for 循环"></a>for 循环</h2><p>对于Go语言来说，循环只有一个<code>for</code>。不过<code>for</code>有多种写法：</p><ol><li>类C语言的三段式for</li><li>类似while的for</li><li>无限循环</li><li>for range</li></ol><p>下面通过几个小例子展示一下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 三段式</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt;= <span class="number">10</span>; i++ &#123;</span><br><span class="line">t.Log(i)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类似while的for，相当于 while n &gt; 0</span></span><br><span class="line">n := <span class="number">5</span></span><br><span class="line"><span class="keyword">for</span> n &gt; <span class="number">0</span> &#123;</span><br><span class="line">t.Log(n)</span><br><span class="line">n--</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 无限循环  while true</span></span><br><span class="line"><span class="comment">// 通常用于服务监听、消费消息队列、重试逻辑、goroutine后台任务</span></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;running&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">for &#123;</span></span><br><span class="line"><span class="comment"> msg := receive()</span></span><br><span class="line"><span class="comment"> if msg == &quot;quit&quot; &#123;</span></span><br><span class="line"><span class="comment">  break</span></span><br><span class="line"><span class="comment"> &#125;</span></span><br><span class="line"><span class="comment"> handle(msg)</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// for range，前面学习slice、map等经常使用</span></span><br><span class="line">nums := []<span class="type">int</span>&#123;<span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;</span><br><span class="line"><span class="keyword">for</span> index, num := <span class="keyword">range</span> nums &#123;</span><br><span class="line">t.Log(index, num)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只要有循环，那就会有<code>break</code>和<code>continue</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForBreak</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10</span>; i++ &#123;</span><br><span class="line"><span class="keyword">if</span> i &gt; <span class="number">5</span> &#123;</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br><span class="line">t.Log(i)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForContinue</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10</span>; i++ &#123;</span><br><span class="line"><span class="keyword">if</span> i%<span class="number">2</span> == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">t.Log(i)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="遍历string"><a href="#遍历string" class="headerlink" title="遍历string"></a>遍历string</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForString</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i, c := <span class="keyword">range</span> <span class="string">&quot;Go语言&quot;</span> &#123;</span><br><span class="line">t.Log(i, c, <span class="type">string</span>(c))</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">for_test.<span class="keyword">go</span>:<span class="number">57</span>: <span class="number">0</span> <span class="number">71</span> G</span><br><span class="line">for_test.<span class="keyword">go</span>:<span class="number">57</span>: <span class="number">1</span> <span class="number">111</span> o</span><br><span class="line">for_test.<span class="keyword">go</span>:<span class="number">57</span>: <span class="number">2</span> <span class="number">35821</span> 语</span><br><span class="line">for_test.<span class="keyword">go</span>:<span class="number">57</span>: <span class="number">5</span> <span class="number">35328</span> 言</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>range string</code> 遍历的是 rune，不是 byte。</p></div><p>如果想按字节遍历</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">s := <span class="string">&quot;Go语言&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line"> fmt.Println(i, s[i])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可参考day2的<code>字符串</code></p><div class="note info simple"><p>遍历数组时，<code>range</code> 会复制数组。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForArray</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">arr := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="comment">// 复制数组，这里的v是元素得副本，修改v不会修原来数组的值</span></span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> arr &#123;</span><br><span class="line">t.Log(v)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//如果数组很大，可以遍历数组指针避免复制</span></span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> &amp;arr &#123;</span><br><span class="line">t.Log(v)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>数组得使用率远不如切片，不过这一问题对于切片，和数组基本上通用。</p><h3 id="range获取地址"><a href="#range获取地址" class="headerlink" title="range获取地址"></a>range获取地址</h3><p>对于<code>Go1.26</code>来说，使用<code>range</code>获取地址不会再像<code>1.22</code>之前那样，出现多个地址可能指向同一个变量得问题，例如在Go 1.22之前，如下代码会出问题：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForPointer1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">nums := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="keyword">var</span> ptrs []*<span class="type">int</span></span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> nums &#123;</span><br><span class="line">ptrs = <span class="built_in">append</span>(ptrs, &amp;v)</span><br><span class="line">&#125;</span><br><span class="line">t.Log(ptrs)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在Go 1.22开始，for range循环变量每次迭代都会创建新的变量，闭包捕获问题被改善，上述代码就不会有问题，但实际开发中，仍然推荐如下写法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">更推荐使用索引方式获取元素地址，避免循环变量v被复用导致所有指针指向同一地址的问题。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForPointer2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">nums := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="keyword">var</span> ptrs []*<span class="type">int</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> nums &#123;</span><br><span class="line">ptrs = <span class="built_in">append</span>(ptrs, &amp;nums[i])</span><br><span class="line">&#125;</span><br><span class="line">t.Log(ptrs)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="labeled-break-continue"><a href="#labeled-break-continue" class="headerlink" title="labeled break&#x2F;continue"></a>labeled break&#x2F;continue</h3><p>和Java一样，Go语言也支持label跳出多层循环</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForLabeledBreak</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">outer:</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line"><span class="keyword">for</span> j := <span class="number">0</span>; j &lt; <span class="number">5</span>; j++ &#123;</span><br><span class="line"><span class="keyword">if</span> i == <span class="number">1</span> &amp;&amp; j == <span class="number">1</span> &#123;</span><br><span class="line"><span class="keyword">break</span> outer</span><br><span class="line">&#125;</span><br><span class="line">t.Log(i, j)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>break outer</code> 会跳出标记为 <code>outer</code> 的循环。如果是<code>continue</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestForLabeledContinue</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">outer:</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line"><span class="keyword">for</span> j := <span class="number">0</span>; j &lt; <span class="number">5</span>; j++ &#123;</span><br><span class="line"><span class="keyword">if</span> i == <span class="number">1</span> &amp;&amp; j == <span class="number">1</span> &#123;</span><br><span class="line"><span class="keyword">continue</span> outer</span><br><span class="line">&#125;</span><br><span class="line">t.Log(i, j)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>continue outer</code> 会直接进入外层循环的下一轮。</p><h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><h3 id="函数语法"><a href="#函数语法" class="headerlink" title="函数语法"></a>函数语法</h3><p>Go语言中的函数，基本上也就是Java中的方法。其基本定义方式如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">add</span><span class="params">(a <span class="type">int</span>, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"> <span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// func + 函数名(参数1 类型，参数2，类型 ...) 返回值类型 &#123;函数体&#125;</span></span><br></pre></td></tr></table></figure><p>对于如果连续多个参数类型相同，可以进行参数类型简写：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">add</span><span class="params">(a,b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="多返回值"><a href="#多返回值" class="headerlink" title="多返回值"></a>多返回值</h3><p>和<code>Java</code>不同的是，Go语言中的函数是支持多个返回值的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回结果+异常</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">div</span><span class="params">(a, b <span class="type">int</span>)</span></span> (<span class="type">int</span>, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> b == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>, fmt.Errorf(<span class="string">&quot;分母不能为0&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> a / b, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMultiReturn</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">result, err := div(<span class="number">10</span>, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">t.Error(err)</span><br><span class="line">&#125;</span><br><span class="line">t.Log(result)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，这里并不是异常处理机制，而是Go语言 使用多返回值来处理错误。</p><p>当然， 返回值也是可以忽略的，前面学习其他知识点的时候都有提及，方法就是使用<code>_</code>忽略不需要的返回值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">value, _ := strconv.Atoi(<span class="string">&quot;123&quot;</span>)</span><br><span class="line">fmt.Println(value)</span><br></pre></td></tr></table></figure><p>一般不建议在实际开发中随便忽略错误。更推荐如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">value,err := strconv.Atoi(<span class="string">&quot;123&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(vlaue)</span><br></pre></td></tr></table></figure><h3 id="命名返回值"><a href="#命名返回值" class="headerlink" title="命名返回值"></a>命名返回值</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetSize</span><span class="params">()</span></span> (width, height <span class="type">int</span>) &#123;</span><br><span class="line">width = <span class="number">100</span></span><br><span class="line">height = <span class="number">200</span></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestNakedReturn</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x, y := GetSize()</span><br><span class="line">t.Log(x, y)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里 <code>return</code> 没有显式返回值，称为 naked return。</p><div class="note info simple"><p>命名返回值适合短函数。复杂函数中不建议大量使用 naked return，可读性差。</p></div><p>如果是复杂函数，则更推荐如下写法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">split</span><span class="params">(sum <span class="type">int</span>)</span></span> (<span class="type">int</span>, <span class="type">int</span>) &#123;</span><br><span class="line">x := sum / <span class="number">2</span></span><br><span class="line">y := sum - x</span><br><span class="line"><span class="keyword">return</span> x, y</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestNakedReturn1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">a, b := split(<span class="number">200</span>)</span><br><span class="line">t.Log(a, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数参数是值传递"><a href="#函数参数是值传递" class="headerlink" title="函数参数是值传递"></a>函数参数是值传递</h3><p>Go 中函数参数默认都是值传递。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">change</span><span class="params">(num <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">num = num * <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethodArgs</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">num := <span class="number">10</span></span><br><span class="line">change(num)</span><br><span class="line">t.Log(num)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>回忆在昨天指针章节学习中，如果要修改原始值，需要传递指针</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changePointer</span><span class="params">(num *<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">*num = *num * <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethodArgs2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">num := <span class="number">10</span></span><br><span class="line">changePointer(&amp;num)</span><br><span class="line">t.Log(num)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>Go 只有值传递。即使传的是 slice、map、channel，本质上也是拷贝它们的描述符或引用头部。</p></div><p>对于<code>slice</code>、<code>map</code>，可参考前一天的介绍，尤其注意<code>append</code>，它返回的是新的<code>slice</code>，所以需要接受返回值。</p><h3 id="可变参数"><a href="#可变参数" class="headerlink" title="可变参数"></a>可变参数</h3><p>和Java一样，Go也支持可变参数，其代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">sum</span><span class="params">(nums ...<span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="comment">// nums ...int 在函数内部是 []int。</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;nums Type: %T&quot;</span>, nums)</span><br><span class="line">total := <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> nums &#123;</span><br><span class="line">total += v</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> total</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethodArray</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">total := sum(<span class="number">10</span>, <span class="number">20</span>, <span class="number">32</span>, <span class="number">423</span>, <span class="number">12</span>, <span class="number">342</span>, <span class="number">54</span>, <span class="number">3</span>)</span><br><span class="line">t.Logf(<span class="string">&quot;Total : %v&quot;</span>, total)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对于slice，在调用的时候后面加...</span></span><br><span class="line">s := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line">t.Logf(<span class="string">&quot;Slice: %v&quot;</span>, sum(s...))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数是一等公民"><a href="#函数是一等公民" class="headerlink" title="函数是一等公民"></a>函数是一等公民</h3><p>在Go语言中，函数是一等公民，可以赋值给变量</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">add := <span class="function"><span class="keyword">func</span><span class="params">(a, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line">t.Log(add(<span class="number">1</span>, <span class="number">2</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>也可以作为参数</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">calculate</span><span class="params">(a, b <span class="type">int</span>, op <span class="keyword">func</span>(<span class="type">int</span>, <span class="type">int</span>)</span></span> <span class="type">int</span>) <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> op(a, b)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">t.Log(calculate(<span class="number">10</span>, <span class="number">20</span>, <span class="function"><span class="keyword">func</span><span class="params">(a, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> a * b</span><br><span class="line">&#125;))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还可以作为返回值</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">multiplier</span><span class="params">(factor <span class="type">int</span>)</span></span> <span class="function"><span class="keyword">func</span><span class="params">(<span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> n * factor</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod3</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">double := multiplier(<span class="number">2</span>)</span><br><span class="line">t.Log(double(<span class="number">10</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>闭包可以捕获外部变量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">counter</span><span class="params">()</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line">count := <span class="number">0</span></span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line">count++</span><br><span class="line"><span class="keyword">return</span> count</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod4</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">next := counter()</span><br><span class="line">t.Log(next())</span><br><span class="line">t.Log(next())</span><br><span class="line">t.Log(next())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>闭包捕获的是变量本身，不只是值。</p></div><h2 id="defer"><a href="#defer" class="headerlink" title="defer"></a>defer</h2><p><code>defer</code> 用于延迟执行函数，常用于释放资源。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">readFile</span><span class="params">()</span></span> <span class="type">error</span> &#123;</span><br><span class="line">file, err := os.Open(<span class="string">&quot;./test.txt&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> file.Close()</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>defer执行顺序</strong>：多个defer按照后进先出执行，也就是栈结构。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod5</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> fmt.Println(<span class="string">&quot;first&quot;</span>)</span><br><span class="line"><span class="keyword">defer</span> fmt.Println(<span class="string">&quot;second&quot;</span>)</span><br><span class="line"><span class="keyword">defer</span> fmt.Println(<span class="string">&quot;third&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>defer参数立即求值</strong></p><p><code>defer</code> 后面的函数如果需要传参，<strong>在 <code>defer</code> 声明的那一刻，参数的值就已经定格了</strong>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod6</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">1</span></span><br><span class="line">    <span class="comment">// 1，此时Go已经知道函数结束时，要打印1</span></span><br><span class="line"><span class="keyword">defer</span> fmt.Println(x)</span><br><span class="line">x = <span class="number">2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为 <code>defer fmt.Println(x)</code> 注册时，就已经把传给函数的参数给固定下来了，而不是等到函数结束，真正执行<code>defer</code>时才去搞清楚<code>x</code>的值。</p><p>如果让其打印2呢？</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod7</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">1</span></span><br><span class="line"><span class="comment">// 1，此时Go已经知道函数结束时，要打印1</span></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">(p *<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">fmt.Println(*p)</span><br><span class="line">&#125;(&amp;x)</span><br><span class="line">x = <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 或者使用闭包也可以实现,因为闭包捕获变量本身。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod8</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">1</span></span><br><span class="line"><span class="comment">// 1，此时Go已经知道函数结束时，要打印1</span></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(x)</span><br><span class="line">&#125;()</span><br><span class="line">x = <span class="number">2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>原因：</em> 注册 <code>defer</code> 时，参数立即求值，求出来的是 <code>x</code> 的内存地址。函数结束时，Go 顺着这个地址去找，发现里面的值已经被改成 <code>2</code> 了。</p><p><code>defer</code>还可以修改命名返回值</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">test</span><span class="params">()</span></span> (result <span class="type">int</span>) &#123;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">result++</span><br><span class="line">&#125;()</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod9</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 2</span></span><br><span class="line">t.Logf(<span class="string">&quot;test() result %v&quot;</span>, test())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行流程：</p><ol><li><code>return 1</code> 先把返回值 <code>result</code> 赋值为 <code>1</code></li><li>执行 defer，<code>result++</code></li><li>真正返回，结果是 <code>2</code></li></ol><h2 id="panic和recover"><a href="#panic和recover" class="headerlink" title="panic和recover"></a>panic和recover</h2><p>在 Go 语言中，<code>panic</code> 和 <code>recover</code> 是处理运行时恐慌（致命错误）的特有机制。它们通常与 <code>defer</code> 配合使用，形成了类似于其他语言中 <code>try-catch-finally</code> 的结构，但其哲学和使用场景有着很大的区别。</p><h3 id="panic"><a href="#panic" class="headerlink" title="panic"></a>panic</h3><p><code>panic</code> 是一个 Go 内置的函数，用来主动抛出程序无法继续运行的致命错误。</p><ul><li><p><strong>触发方式：</strong> 可以是<strong>程序自动触发</strong>（如空指针引用、数组越界、除以 0），也可以是开发者<strong>手动调用</strong> <code>panic()</code>。</p></li><li><p><strong>执行流程：</strong> 当一个函数发生 <code>panic</code> 时，当前函数的正常执行流程会立即中断，但<strong>已注册的 <code>defer</code> 函数依然会被逐个执行</strong>。接着，<code>panic</code> 会沿着调用栈一层层向上抛出，直到程序崩溃并打印出堆栈信息。</p></li></ul><h3 id="recover"><a href="#recover" class="headerlink" title="recover"></a>recover</h3><p><code>recover</code> 也是一个内置函数，用来“拦截”并捕获 <code>panic</code>，让程序不至于崩溃。</p><ul><li><p><strong>使用限制：</strong> <code>recover()</code> <strong>必须在 <code>defer</code> 调用的函数中直接执行</strong>。在正常执行流程中调用 <code>recover</code> 会直接返回 <code>nil</code> 且毫无作用。</p></li><li><p><strong>执行效果：</strong> 如果在 <code>defer</code> 中捕获到了 <code>panic</code>，程序会停止向上崩溃，并恢复正常的执行逻辑（继续执行该 <code>defer</code> 之后的代码，或者安全退出当前函数）。</p></li></ul><h3 id="核心规则"><a href="#核心规则" class="headerlink" title="核心规则"></a>核心规则</h3><ol><li><p><strong><code>recover</code> 必须写在 <code>defer</code> 中</strong>：直接写在函数体里的 <code>recover()</code> 是无法捕获任何恐慌的。</p></li><li><p><strong><code>recover</code> 只能捕获同一个 Goroutine 的 <code>panic</code></strong>：Go 语言的恐慌是协程（Goroutine）隔离的。如果 A 协程发生了 <code>panic</code>，B 协程里的 <code>defer recover</code> 是管不着的，程序依然会挂掉。</p></li><li><p><strong>不要滥用 <code>panic</code></strong>：Go 官方推崇的错误处理方式是返回 <code>error</code>。<code>panic</code> 应该只用于<strong>真正致命的、无法挽回的错误</strong>（例如：数据库初始化失败、配置解析失败等）。</p></li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">safeRun</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">if</span> err := <span class="built_in">recover</span>(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">            fmt.Println(<span class="string">&quot;recover:&quot;</span>,err)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;()</span><br><span class="line">    <span class="built_in">panic</span>(<span class="string">&quot;oom&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另一个小例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMethod11</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot; running ...&quot;</span>)</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := <span class="built_in">recover</span>(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;成功捕获异常，recover : %v\n&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">&quot;OutOfMemory&quot;</span>)</span><br><span class="line">fmt.Println(<span class="string">&quot; panic之后的代码不会被执行&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="init-函数"><a href="#init-函数" class="headerlink" title="init 函数"></a>init 函数</h2><p>看样子有点像Java的<code>static&#123;&#125;</code></p><p>特点：</p><ul><li><p>没有参数</p></li><li><p>没有返回值</p></li><li><p>自动执行</p></li><li><p>早于 <code>main</code></p></li><li><p>一个包中可以有多个 <code>init</code></p></li></ul><p>执行顺序大致是：</p><ol><li>初始化导入的包</li><li>初始化包级变量</li><li>执行 <code>init</code></li><li>执行 <code>main</code></li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Init。。。。&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行任意一个测试用例，即可输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Init。。。。</span><br><span class="line">=== RUN   TestMethod11</span><br><span class="line"> running ...</span><br><span class="line">成功捕获异常，recover : OutOfMemory</span><br><span class="line">--- PASS: TestMethod11 (0.00s)</span><br></pre></td></tr></table></figure><p>如果导入某一个包</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="string">&quot;github.com/go-sql-driver/mysql&quot;</span> <span class="comment">// 这会触发该包的 init() 来注册驱动</span></span><br></pre></td></tr></table></figure><p>那么init函数和Java的static代码块有啥区别呢？</p><table><thead><tr><th><strong>特性</strong></th><th><strong>Go 的 init 函数</strong></th><th><strong>Java 的 static 静态块</strong></th></tr></thead><tbody><tr><td><strong>依附对象</strong></td><td>Package（包级别）</td><td>Class（类级别）</td></tr><tr><td><strong>执行时机</strong></td><td><code>main</code> 函数运行前，包被加载时</td><td>类被加载（Class Load）时</td></tr><tr><td><strong>手动调用</strong></td><td>不允许</td><td>不允许</td></tr><tr><td><strong>编写数量</strong></td><td>同一个包&#x2F;文件内可以写<strong>多个</strong></td><td>同一个类里可以写多个（但极少这么做）</td></tr><tr><td><strong>主要用途</strong></td><td>初始化包级变量、环境检查、注册驱动</td><td>初始化静态成员变量</td></tr></tbody></table>]]>
    </content>
    <id>https://dev.net.cn/learning-go-04/</id>
    <link href="https://dev.net.cn/learning-go-04/"/>
    <published>2026-04-03T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天简单的学习下流程控制与函数，这一块大多数语言都差不多。</p>
<h2 id="if-else"><a href="#if-else" class="headerlink" title="if&#x2F;else"></a>if&#x2F;else</h2><p>Go语]]>
    </summary>
    <title>Go语言学习笔记（四）- 控制流与函数</title>
    <updated>2026-04-03T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<p><strong>本章知识点：</strong></p><ul><li>数组与切片</li><li>map（字典）</li><li>struct（结构体）</li><li>指针</li><li>泛型</li></ul><span id="more"></span><h2 id="数组与切片"><a href="#数组与切片" class="headerlink" title="数组与切片"></a>数组与切片</h2><p>在 Go 中，<strong>数组</strong>是固定长度的，而<strong>切片</strong>是动态的、是对数组的抽象。</p><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>不管在<code>Java</code>还是<code>Go</code>似乎都不太常用，比较常用<code>List</code>或者Go中的<code>切片</code>。其语法如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_Array</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组时值类型，当一个数组被赋值给另一个数组时，实际上是创建了一个新的数组，并将原数组的值复制到新数组中。这意味着对新数组的修改不会影响原数组，反之亦然。</span></span><br><span class="line"><span class="comment">// 定义一个长度为5的int数组，其默认值为0</span></span><br><span class="line"><span class="keyword">var</span> arr1 [<span class="number">5</span>]<span class="type">int</span></span><br><span class="line">t.Log(arr1)</span><br><span class="line"><span class="comment">// 定义一个长度为3的字符串数组，默认值为&quot;&quot;</span></span><br><span class="line"><span class="keyword">var</span> arr2 [<span class="number">3</span>]<span class="type">string</span></span><br><span class="line">t.Log(arr2)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//字面量初始化</span></span><br><span class="line">arr3 := [<span class="number">3</span>]<span class="type">string</span>&#123;<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>&#125;</span><br><span class="line">t.Log(arr3)</span><br><span class="line"></span><br><span class="line"><span class="comment">//省略长度，让编译器自动推断</span></span><br><span class="line">arr4 := []<span class="type">string</span>&#123;<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>&#125;</span><br><span class="line">t.Log(<span class="built_in">len</span>(arr4))</span><br><span class="line"></span><br><span class="line"><span class="comment">//指定下标初始化</span></span><br><span class="line">arr5 := [<span class="number">5</span>]<span class="type">int</span>&#123;<span class="number">1</span>: <span class="number">100</span>, <span class="number">2</span>: <span class="number">200</span>&#125;</span><br><span class="line">t.Log(arr5)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>访问数组</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_ArrayOpera</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 定义一个长度为5的int数组，其默认值为0</span></span><br><span class="line"><span class="keyword">var</span> arr1 [<span class="number">5</span>]<span class="type">int</span></span><br><span class="line"><span class="comment">// 给指定下表赋值/或者是修改</span></span><br><span class="line">arr1[<span class="number">0</span>] = <span class="number">10</span></span><br><span class="line">arr1[<span class="number">3</span>] = <span class="number">88</span></span><br><span class="line">t.Log(arr1)</span><br><span class="line"><span class="comment">// 获取数组指定下标的值</span></span><br><span class="line">t.Log(arr1[<span class="number">3</span>])</span><br><span class="line"><span class="comment">//获取数组长度</span></span><br><span class="line">t.Log(<span class="built_in">len</span>(arr1))</span><br><span class="line"></span><br><span class="line"><span class="comment">//二维数组</span></span><br><span class="line"><span class="keyword">var</span> arrab [<span class="number">2</span>][<span class="number">3</span>]<span class="type">string</span></span><br><span class="line">arrab[<span class="number">0</span>][<span class="number">0</span>] = <span class="string">&quot;a&quot;</span></span><br><span class="line">arrab[<span class="number">0</span>][<span class="number">1</span>] = <span class="string">&quot;b&quot;</span></span><br><span class="line">arrab[<span class="number">0</span>][<span class="number">2</span>] = <span class="string">&quot;c&quot;</span></span><br><span class="line"><span class="comment">// invalid argument: index 3 out of bounds [0:3]</span></span><br><span class="line"><span class="comment">// arrab[0][3] = &quot;c&quot;</span></span><br><span class="line">arrab[<span class="number">1</span>][<span class="number">0</span>] = <span class="string">&quot;d&quot;</span></span><br><span class="line">t.Log(arrab)</span><br><span class="line"></span><br><span class="line"><span class="comment">//二维数组初始化</span></span><br><span class="line">arrab1 := [<span class="number">2</span>][<span class="number">3</span>]<span class="type">string</span>&#123;</span><br><span class="line">&#123;<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>&#125;,</span><br><span class="line"><span class="comment">// 必须有,结尾，否则会报错：syntax error: unexpected newline, expecting &#125;</span></span><br><span class="line">&#123;<span class="string">&quot;a1&quot;</span>, <span class="string">&quot;b1&quot;</span>, <span class="string">&quot;c1&quot;</span>&#125;,</span><br><span class="line">&#125;</span><br><span class="line">t.Log(arrab1)</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取指定下标</span></span><br><span class="line">t.Log(arrab1[<span class="number">0</span>][<span class="number">1</span>])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code> 对于数组的便利，下一章会有介绍。</code></p><p>关于数组，还有一个最主要的特性：</p><div class="note info simple"><p>Go 中数组是值类型，赋值或传参会复制整个数组，处理大数据时非常低效</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_ArrayCopy</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">arr := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="comment">// 完整的拷贝</span></span><br><span class="line">b := arr</span><br><span class="line">b[<span class="number">0</span>] = <span class="number">6</span></span><br><span class="line">t.Log(arr, b)</span><br><span class="line"><span class="comment">//调用后，不影响arr的值</span></span><br><span class="line">modifyArray(arr)</span><br><span class="line">t.Log(arr)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">modifyArray</span><span class="params">(arr [3]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">//这里修改的是副本，不影响原来的数组</span></span><br><span class="line">arr[<span class="number">0</span>] = <span class="number">6</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>数组的长度必须是常量，且是类型的一部分。<code>[3]int</code> 和 <code>[5]int</code> 在编译器看来是完全不同的两种类型，不能互相赋值或比较。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_ArrayCompare</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">arr1 := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">arr2 := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">arr3 := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span>&#125;</span><br><span class="line">t.Log(arr1 == arr2)</span><br><span class="line">t.Log(arr1 == arr3)</span><br><span class="line"></span><br><span class="line"><span class="comment">//arr4 := [4]int&#123;1, 2, 3, 4&#125;</span></span><br><span class="line"><span class="comment">// 无效运算: arr1 == arr4(类型 [3]int 和 [4]int 不匹配)</span></span><br><span class="line"><span class="comment">//t.Log(arr1 == arr4)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="切片（重点）"><a href="#切片（重点）" class="headerlink" title="切片（重点）"></a>切片（重点）</h3><p>切片是 Go 最核心的设计之一。它不是动态数组，而是<strong>对底层数组的描述符</strong>。</p><p>切片三要素：</p><ul><li>指针（指向底层数组的起始位置）</li><li>长度（len，当前可见的元素个数）</li><li>容量（cap，从起始位置到底层数组末尾的元素个数）</li></ul><p>它的底层结构体<code>runtime/slice.go</code>:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> slice <span class="keyword">struct</span> &#123;</span><br><span class="line">    array unsafe.Pointer <span class="comment">// 指向底层数组的指针</span></span><br><span class="line">    <span class="built_in">len</span>   <span class="type">int</span>            <span class="comment">// 长度：当前切片内的元素个数</span></span><br><span class="line">    <span class="built_in">cap</span>   <span class="type">int</span>            <span class="comment">// 容量：从切片指针开始到数组末尾的元素个数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>当你传递切片时，实际复制的是这个 header，底层数组是共享的。</p></div><p>下面是切片的初始化方式</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_Slice</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">//nil切片</span></span><br><span class="line"><span class="keyword">var</span> s1 []<span class="type">int</span></span><br><span class="line"><span class="comment">// 空切片</span></span><br><span class="line">s2 := []<span class="type">int</span>&#123;&#125;</span><br><span class="line"><span class="comment">//字面量创建</span></span><br><span class="line">s3 := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="comment">//[] [] [1 2 3]</span></span><br><span class="line">t.Log(s1, s2, s3)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用make创建 长度为3，容量为10</span></span><br><span class="line">s4 := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">3</span>, <span class="number">10</span>)</span><br><span class="line"><span class="comment">// 长度为3，容量为3</span></span><br><span class="line">s5 := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">3</span>)</span><br><span class="line">t.Log(s4, s5)</span><br><span class="line"></span><br><span class="line"><span class="comment">//从数组中创建</span></span><br><span class="line">arr := [<span class="number">4</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;</span><br><span class="line">s6 := arr[<span class="number">1</span>:<span class="number">3</span>]</span><br><span class="line">t.Log(s6)</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用new创建  返回 *[]int，需要解引用</span></span><br><span class="line">s7 := <span class="built_in">new</span>([]<span class="type">int</span>)</span><br><span class="line"><span class="comment">//&amp;[]</span></span><br><span class="line">t.Log(s7)</span><br><span class="line"></span><br><span class="line"><span class="comment">//俄罗斯套娃，从切片中创建切片</span></span><br><span class="line">s8 := s3[<span class="number">1</span>:<span class="number">3</span>]</span><br><span class="line">t.Log(s8)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看样子，切片很像<code>Java</code>中的<code>List</code>，顺序存储、动态扩容。它和数组的区别是：</p><table><thead><tr><th>特性</th><th>数组</th><th>切片</th></tr></thead><tbody><tr><td>长度</td><td>固定，是类型的一部分</td><td>可变</td></tr><tr><td>类型</td><td>值类型</td><td>引用类型（底层是结构体）</td></tr><tr><td>传参开销</td><td>复制整个数组</td><td>复制头信息（24字节）</td></tr><tr><td>动态扩展</td><td>不支持</td><td>支持 append</td></tr><tr><td>比较</td><td>支持 &#x3D;&#x3D;</td><td>不支持 &#x3D;&#x3D;（只能和 nil 比）</td></tr></tbody></table><p><strong>切片表达式</strong></p><p>规则：</p><ul><li>0 &lt;&#x3D; low &lt;&#x3D; high &lt;&#x3D; max &lt;&#x3D; cap(底层数组)</li><li>省略 low 默认为 0</li><li>省略 high 默认为 len</li><li>完整形式 [low:high:max] 限制新切片的容量</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_SliceReg</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">//切片是引用类型，当一个切片被赋值给另一个切片时，实际上是创建了一个新的切片，但它们指向同一个底层数组。这意味着对新切片的修改会影响原切片，反之亦然。</span></span><br><span class="line"><span class="comment">// 语法 slice[low:high:max]</span></span><br><span class="line">arr := [<span class="number">10</span>]<span class="type">int</span>&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>&#125;</span><br><span class="line"></span><br><span class="line">s1 := arr[<span class="number">2</span>:<span class="number">5</span>]</span><br><span class="line"><span class="comment">// 2 3 4   len=3 cap=8</span></span><br><span class="line">t.Log(s1)</span><br><span class="line"></span><br><span class="line">s2 := arr[<span class="number">2</span>:<span class="number">5</span>:<span class="number">7</span>]</span><br><span class="line"><span class="comment">// 2, 3, 4   len=3 cap=5 max-low</span></span><br><span class="line">t.Log(s2)</span><br><span class="line"></span><br><span class="line">s3 := arr[:<span class="number">5</span>] <span class="comment">//省略 low</span></span><br><span class="line"><span class="comment">// 0,1,2,3,4</span></span><br><span class="line">t.Log(s3)</span><br><span class="line"></span><br><span class="line">s4 := arr[<span class="number">5</span>:] <span class="comment">//省略 high</span></span><br><span class="line"><span class="comment">// 5,6,7,8,9</span></span><br><span class="line">t.Log(s4)</span><br><span class="line"></span><br><span class="line">s5 := arr[:] <span class="comment">// 完整拷贝</span></span><br><span class="line"><span class="comment">// 0,1,2,3,4,5,6,7,8,9</span></span><br><span class="line">t.Log(s5)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>切片Append</strong></p><div class="note info simple"><p>append函数会返回一个新的切片，新的切片可能指向同一个底层数组，也可能指向一个新的底层数组，这取决于原切片的容量和追加元素的数量。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_SliceAppend</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s1 := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">    <span class="comment">// 追加一个</span></span><br><span class="line">s2 := <span class="built_in">append</span>(s1, <span class="number">4</span>)</span><br><span class="line">    <span class="comment">// 追加多个</span></span><br><span class="line">s3 := <span class="built_in">append</span>(s1, <span class="number">5</span>, <span class="number">6</span>)</span><br><span class="line">    <span class="comment">// 追加另一个切片</span></span><br><span class="line">s4 := <span class="built_in">append</span>(s1, []<span class="type">int</span>&#123;<span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>&#125;...)</span><br><span class="line">t.Log(s1, s2, s3, s4)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果容量足够，就会出现共享底层数组的情况：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_ShareArray</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">a := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line">b := a[:<span class="number">3</span>]</span><br><span class="line">b = <span class="built_in">append</span>(b, <span class="number">99</span>)</span><br><span class="line">t.Log(a, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   Test_ShareArray</span><br><span class="line">    array_test.go:206: [1 2 3 99 5] [1 2 3 99]</span><br></pre></td></tr></table></figure><div class="note info simple"><p>解决方案：使用完整切片表达式限制容量</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_ShareArray</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">a := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line"><span class="comment">//b := a[:3]</span></span><br><span class="line"><span class="comment">//b = append(b, 99)</span></span><br><span class="line">b := a[:<span class="number">3</span>:<span class="number">3</span>] <span class="comment">// len=3,cap=3，append会创建新底层数组，不影响a</span></span><br><span class="line">b = <span class="built_in">append</span>(b, <span class="number">99</span>)</span><br><span class="line">t.Log(a, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时，输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   Test_ShareArray</span><br><span class="line">    array_test.go:208: [1 2 3 4 5] [1 2 3 99]</span><br></pre></td></tr></table></figure><p>还可能出现<code>append</code>以后，原切片可能被覆盖的情况</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_OverwriteSlice</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">3</span>, <span class="number">5</span>)</span><br><span class="line">s1 := <span class="built_in">append</span>(s, <span class="number">1</span>)</span><br><span class="line">s2 := <span class="built_in">append</span>(s, <span class="number">2</span>)</span><br><span class="line">t.Log(s, s1, s2)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   Test_OverwriteSlice</span><br><span class="line">    array_test.go:215: [0 0 0] [0 0 0 2] [0 0 0 2]</span><br></pre></td></tr></table></figure><p>此时可以看到，s2覆盖了s1的最后一个元素。原因就是s1和s2指向了同一个底层数组。</p><div class="note info simple"><p>解决方案：在 append 前对原切片做一次完整拷贝，让每次 append 独占底层数组</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_OverwriteSlice</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">3</span>, <span class="number">5</span>)</span><br><span class="line"><span class="comment">//s1 := append(s, 1)</span></span><br><span class="line"><span class="comment">//s2 := append(s, 2)</span></span><br><span class="line">s1 := <span class="built_in">append</span>(slices.Clone(s), <span class="number">1</span>)</span><br><span class="line">s2 := <span class="built_in">append</span>(slices.Clone(s), <span class="number">2</span>)</span><br><span class="line">t.Log(s, s1, s2)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>切片扩容</strong></p><p>既然涉及到自动扩容，那就要了解下扩容的原理：</p><p>当执行 <code>s = append(s, val)</code> 时，Go 的扩容策略在不同版本有所微调（以 <code>Go 1.25</code> 为准）：</p><ol><li>如果新长度大于容量的 2 倍，直接使用新长度作为新容量。</li><li>否则，如果旧容量 &lt; 256，翻倍。</li><li>如果旧容量 &gt;&#x3D; 256，则增加 <code>(old_cap + 3*256) / 4</code>，直到满足要求。</li><li><strong>注意</strong>：扩容会触发内存重新分配和数据拷贝，旧底层数组将被 GC 回收。</li></ol><div class="note warning modern"><p>扩容可能导致底层数组重新分配！此时原切片和新切片不再共享数据。</p></div><p>例如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">a := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">b := <span class="built_in">append</span>(a, <span class="number">4</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果 a 的容量足够，b 和 a 共享底层数组</span></span><br><span class="line"><span class="comment">// 如果容量不够，b 指向全新数组，a 不受影响</span></span><br></pre></td></tr></table></figure><p><strong>切片的拷贝</strong></p><div class="note info simple"><p>copy函数会将源切片中的元素复制到目标切片中，复制的元素数量取决于源切片和目标切片的长度。copy函数返回实际复制的元素数量。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_SliceCopy</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ss := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line">sd := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">3</span>)</span><br><span class="line"><span class="comment">// 将ss（src）切片中的前3个元素复制到sd(dst)切片中</span></span><br><span class="line">n := <span class="built_in">copy</span>(sd, ss)</span><br><span class="line">t.Log(sd, n)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>特点：</p><ul><li>复制个数 &#x3D; <code>min(len(src), len(dst))</code></li><li>两个切片可以重叠</li><li>可用于切片截断：<code>copy(s, s[i:])</code></li></ul><p>常用技巧：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 删除索引 i 的元素</span></span><br><span class="line"><span class="built_in">copy</span>(s[i:], s[i+<span class="number">1</span>:])</span><br><span class="line">s = s[:<span class="built_in">len</span>(s)<span class="number">-1</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在开头插入元素（效率低，需要移动所有元素）</span></span><br><span class="line">s = <span class="built_in">append</span>([]<span class="type">int</span>&#123;x&#125;, s...)</span><br><span class="line"></span><br><span class="line"><span class="comment">//解决大切片不释放内存的问题</span></span><br><span class="line"><span class="comment">// 假设 data是一个超级大的切片</span></span><br><span class="line">small := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">10</span>)</span><br><span class="line"><span class="comment">//只copy出需要的部分，否则只要small存在，data的整个底层数组都不会被GC</span></span><br><span class="line"><span class="built_in">copy</span>(small, data)</span><br></pre></td></tr></table></figure><div class="note info simple"><p>切片是引用类型，函数内修改会影响原切片（注意区分修改元素和改变切片本身）：</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_SliceArgs</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line">modifySlice(s)</span><br><span class="line">t.Log(s)</span><br><span class="line"></span><br><span class="line">modifySlice1(s)</span><br><span class="line">t.Log(s)</span><br><span class="line"></span><br><span class="line">appendValue(&amp;s, <span class="number">8</span>)</span><br><span class="line">t.Log(s)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">modifySlice</span><span class="params">(s []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">// 会改变原始切片的值</span></span><br><span class="line">s[<span class="number">0</span>] = <span class="number">99</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">modifySlice1</span><span class="params">(s []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">// 不会改变原始切片的值</span></span><br><span class="line">s = <span class="built_in">append</span>(s, <span class="number">6</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果想要实现切片的append，需要传递指针</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">appendValue</span><span class="params">(s *[]<span class="type">int</span>, v <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> s == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">*s = <span class="built_in">append</span>(*s, v)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>多维切片</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_MultiSlice</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 多维切片</span></span><br><span class="line"><span class="keyword">var</span> s [][]<span class="type">int</span></span><br><span class="line"><span class="comment">// 切片的每行长度可以不同（俗称锯齿数组）</span></span><br><span class="line">rows, cols := <span class="number">3</span>, <span class="number">4</span></span><br><span class="line">s = <span class="built_in">make</span>([][]<span class="type">int</span>, rows, cols)</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> s &#123;</span><br><span class="line">s[i] = <span class="built_in">make</span>([]<span class="type">int</span>, cols)</span><br><span class="line">&#125;</span><br><span class="line">s[<span class="number">1</span>][<span class="number">2</span>] = <span class="number">99</span></span><br><span class="line"></span><br><span class="line">t.Log(s)</span><br><span class="line">    <span class="comment">// [[0 0 0 0] [0 0 99 0] [0 0 0 0]]</span></span><br><span class="line"><span class="comment">// 切片的每行长度可以不同（俗称锯齿数组）</span></span><br><span class="line">s2 := [][]<span class="type">int</span>&#123;</span><br><span class="line">&#123;<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="number">1</span>, <span class="number">2</span>&#125;,</span><br><span class="line">&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;,</span><br><span class="line">&#125;</span><br><span class="line">t.Log(s2)</span><br><span class="line">    <span class="comment">// [[1] [1 2] [1 2 3]]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="map-字典"><a href="#map-字典" class="headerlink" title="map(字典)"></a>map(字典)</h2><p><code>map</code> 是 Go 内置的哈希表类型，用于存储 <strong>键值对</strong>。其语法结构为</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">map</span>[KeyType]ValueType</span><br></pre></td></tr></table></figure><h3 id="申明与初始化"><a href="#申明与初始化" class="headerlink" title="申明与初始化"></a>申明与初始化</h3><p><strong>nil map</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// nil map，不能写入</span></span><br><span class="line"><span class="keyword">var</span> m1 <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span></span><br></pre></td></tr></table></figure><div class="note info simple"><p>map 的零值是 nil</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> m1 <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span></span><br><span class="line">t.Logf(<span class="string">&quot;m1 == nil 的结果是 : %v&quot;</span>, m1 == <span class="literal">nil</span>)</span><br><span class="line"><span class="comment">// m1 == nil 的结果是 : true</span></span><br></pre></td></tr></table></figure><p><code>nil不能写入</code>，但可以有以下操作：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_NilMap</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> m <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span></span><br><span class="line"><span class="comment">// nil map可以获取值</span></span><br><span class="line">v := m[<span class="string">&quot;x&quot;</span>]</span><br><span class="line">t.Logf(<span class="string">&quot;nil map get key : %v&quot;</span>, v)</span><br><span class="line"><span class="comment">// nil map 可以作为判断</span></span><br><span class="line">_, ok := m[<span class="string">&quot;x&quot;</span>]</span><br><span class="line">t.Logf(<span class="string">&quot;nil map get status : %v&quot;</span>, ok)</span><br><span class="line"><span class="comment">// nil map可以删除元素</span></span><br><span class="line"><span class="built_in">delete</span>(m, <span class="string">&quot;x&quot;</span>)</span><br><span class="line"><span class="comment">// nil map可以clear</span></span><br><span class="line">clear(m)</span><br><span class="line"><span class="comment">// nil map可以遍历</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> m &#123;</span><br><span class="line">t.Log(i)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>通过make创建map</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">m := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line"><span class="comment">// 或者指定容量</span></span><br><span class="line">m1:= <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>,<span class="number">1000</span>)</span><br></pre></td></tr></table></figure><div class="note info simple"><p>注意：这个 <code>1000</code> 是 <strong>容量提示</strong>，不是最大容量限制。map会自动扩容。</p></div><p><strong>通过字面量创建map</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span> &#123;</span><br><span class="line">    <span class="string">&quot;a&quot;</span>:<span class="number">1</span>,</span><br><span class="line">    <span class="string">&quot;b&quot;</span>:<span class="number">2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，也可以创建一个空的map（注意：空map和<code>nil map</code>是两码事），空map可以写入数据。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>此处附上<strong>nil map</strong> 与 <strong>空map</strong>对比：</p><table><thead><tr><th></th><th>nil map</th><th>空map <code>map[k]v&#123;&#125;</code></th></tr></thead><tbody><tr><td>读取</td><td>返回零值</td><td>返回零值</td></tr><tr><td>写入</td><td>panic</td><td>正常</td></tr><tr><td>删除</td><td>panic</td><td>正常</td></tr><tr><td>len</td><td>0</td><td>0</td></tr></tbody></table><h3 id="map的CRUD"><a href="#map的CRUD" class="headerlink" title="map的CRUD"></a>map的CRUD</h3><p><strong>增加&#x2F;修改</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;&#125;</span><br><span class="line"><span class="comment">// 新增一条数据</span></span><br><span class="line">m[<span class="string">&quot;age&quot;</span>] = <span class="number">18</span></span><br><span class="line"><span class="comment">// 将原有的数据修改为28</span></span><br><span class="line">m[<span class="string">&quot;age&quot;</span>] = <span class="number">28</span> </span><br></pre></td></tr></table></figure><p>总结下来就是：</p><ul><li>如果key不存在，就是新增。</li><li>如果key存在，就是修改。</li></ul><p><strong>读取</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">v := m[<span class="string">&quot;age&quot;</span>] </span><br><span class="line"><span class="comment">// 此时输出28</span></span><br><span class="line">t.Log(v)</span><br><span class="line"><span class="comment">// 如果key不存在，则返回value类型零值</span></span><br><span class="line">v1 := m[<span class="string">&quot;age1&quot;</span>]</span><br><span class="line"><span class="comment">// 输出0</span></span><br><span class="line">t.Log(v)</span><br></pre></td></tr></table></figure><p>判断key是否存在（核心写法）</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">v,ok := m[<span class="string">&quot;age&quot;</span>]</span><br><span class="line"><span class="keyword">if</span> ok &#123;</span><br><span class="line">    t.Log(<span class="string">&quot;存在&quot;</span>,v)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    t.Log(<span class="string">&quot;不存在&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>删除元素</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">delete</span>(m,<span class="string">&quot;age&quot;</span>)</span><br><span class="line"><span class="comment">// 即使key不存在也不会报错，所以nil map使用delete也不会报错。</span></span><br><span class="line"><span class="built_in">delete</span>(m, <span class="string">&quot;not-exist&quot;</span>)</span><br></pre></td></tr></table></figure><p><strong>清空map</strong></p><p>此特性是<code>Go 1.21</code>之后内置的函数<code>clear()</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clear(m)</span><br></pre></td></tr></table></figure><p>删除map中的所有元素，对于<code>nil map</code>调用也安全的。</p><p><strong>获取长度</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回的是当前key-value对的数量。</span></span><br><span class="line"><span class="built_in">len</span>(m)</span><br></pre></td></tr></table></figure><p><strong>遍历map</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">    <span class="string">&quot;key1&quot;</span>:<span class="number">100</span>,</span><br><span class="line">    <span class="string">&quot;key2&quot;</span>:<span class="number">150</span>,</span><br><span class="line">    <span class="string">&quot;key3&quot;</span>:<span class="number">200</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> k,v := <span class="keyword">range</span> m &#123;</span><br><span class="line">    t.Logf(<span class="string">&quot;key = %v, value = %v&quot;</span>,k,v)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只遍历key</span></span><br><span class="line"><span class="keyword">for</span> k:= <span class="keyword">range</span> m &#123;</span><br><span class="line">    t.Logf(<span class="string">&quot;key = %v&quot;</span>,k)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只便利value</span></span><br><span class="line"><span class="keyword">for</span> _,v := <span class="keyword">range</span> m &#123;</span><br><span class="line">    t.Logf(<span class="string">&quot;value = %v&quot;</span>,v)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>注意：map 遍历顺序是<code>不确定</code>的</p></div><p>那么如何确保输出顺序是稳定的呢？那就是先取key，在排序：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_LoopMapSort</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">m := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line"><span class="string">&quot;key1&quot;</span>: <span class="number">100</span>,</span><br><span class="line"><span class="string">&quot;key2&quot;</span>: <span class="number">150</span>,</span><br><span class="line"><span class="string">&quot;key3&quot;</span>: <span class="number">200</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> k, v := <span class="keyword">range</span> m &#123;</span><br><span class="line">t.Log(k, v)</span><br><span class="line">&#125;</span><br><span class="line">keys := <span class="built_in">make</span>([]<span class="type">string</span>, <span class="number">0</span>, <span class="built_in">len</span>(m))</span><br><span class="line"><span class="keyword">for</span> k := <span class="keyword">range</span> m &#123;</span><br><span class="line">keys = <span class="built_in">append</span>(keys, k)</span><br><span class="line">&#125;</span><br><span class="line">sort.Strings(keys)</span><br><span class="line"><span class="keyword">for</span> _, k := <span class="keyword">range</span> keys &#123;</span><br><span class="line">t.Logf(<span class="string">&quot;key = %v, value = %v&quot;</span>, k, m[k])</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>多次执行，观察输出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   Test_LoopMapSort</span><br><span class="line">    map_test.go:71: key3 200</span><br><span class="line">    map_test.go:71: key1 100</span><br><span class="line">    map_test.go:71: key2 150</span><br><span class="line">    map_test.go:80: key = key1, value = 100</span><br><span class="line">    map_test.go:80: key = key2, value = 150</span><br><span class="line">    map_test.go:80: key = key3, value = 200</span><br></pre></td></tr></table></figure><p>对于Go语言的map，它对key还是略微有一些要求的，那就是map的key必须是可比较的类型，也就是说可以使用<code>==</code>或者<code>!=</code>比较的类型，以下类型都可以作为map的key:</p><ul><li>string</li><li>int</li><li>bool</li><li>float64</li><li>complex64</li><li>pointer</li><li>channel</li><li>interface</li><li>array</li><li>struct</li></ul><p>其中<code>array</code>和<code>struct</code>的元素&#x2F;字段也必须为可比较。</p><p>下面三个是不能作为key的类型：</p><ul><li>slice</li><li>map</li><li>func</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_MapKeyType</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">m := <span class="keyword">map</span>[[<span class="number">2</span>]<span class="type">int</span>]<span class="type">string</span>&#123;</span><br><span class="line">&#123;<span class="number">1</span>, <span class="number">2</span>&#125;: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">&#123;<span class="number">3</span>, <span class="number">4</span>&#125;: <span class="string">&quot;2&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">m1 := <span class="keyword">map</span>[<span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">Age  <span class="type">int</span></span><br><span class="line">&#125;]<span class="type">string</span>&#123;</span><br><span class="line">&#123;<span class="string">&quot;zhangsan&quot;</span>, <span class="number">18</span>&#125;: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">&#123;<span class="string">&quot;lisi&quot;</span>, <span class="number">18</span>&#125;:     <span class="string">&quot;2&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">t.Log(m, m1)</span><br><span class="line"></span><br><span class="line"><span class="comment">//无效的映射键类型: 必须为键类型完全定义比较运算符 == 和 !=</span></span><br><span class="line"><span class="comment">// m2 := map[func()]string&#123;&#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至于<code>slice</code>不能作为map的key，主要原因是slice不能使用<code>==</code>比较</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">b := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="comment">//无效运算: a == b (在 []int 中未定义运算符 ==)</span></span><br><span class="line">t.Log(a == b)</span><br></pre></td></tr></table></figure><p>因为slice底层有三个属性<code>指针</code>、<code>长度</code>、<code>容量</code>，Go语言没有定义两个slice内容相等的比较语义。所以<code>slice</code>不能作为map的key。如果想用 slice 内容作为 key，可以转成 string 或 array。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">m3 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;&#125;</span><br><span class="line">key := fmt.Sprint([]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;)</span><br><span class="line">m3[key] = <span class="number">11</span></span><br><span class="line">t.Log(m3)</span><br><span class="line"><span class="comment">// 或者固定长度</span></span><br><span class="line">m4 := <span class="keyword">map</span>[[<span class="number">3</span>]<span class="type">int</span>]<span class="type">string</span>&#123;&#125;</span><br><span class="line">m4[[<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;] = <span class="string">&quot;ok&quot;</span></span><br><span class="line">t.Log(m4)</span><br></pre></td></tr></table></figure><p>还需要注意的是，两个map不能直接比较，但任意一个map可以和<code>nil map</code>比较，如果需要比较两个map，可以使用<code>maps.Equal(m1,m2)</code>来实现（注意，此时需要valuie也是可比较的才行），或者根据业务需求，自己定义比较逻辑。</p><h3 id="map-是引用类型吗？"><a href="#map-是引用类型吗？" class="headerlink" title="map 是引用类型吗？"></a>map 是引用类型吗？</h3><div class="note info simple"><p>Go 语言规范并没有把 <code>map</code> 正式定义为“引用类型”这一类别，但规范明确说明：非 nil 的 map 值包含对底层数据的引用；具体来说，map 值是对实现相关数据结构的引用。因此从使用效果看，map 赋值或传参会复制这个引用，而不会复制整张 map。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_Map1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">m1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line"><span class="string">&quot;a&quot;</span>: <span class="number">1</span>,</span><br><span class="line">&#125;</span><br><span class="line">m2 := m1</span><br><span class="line">m2[<span class="string">&quot;a&quot;</span>] = <span class="number">100</span></span><br><span class="line"><span class="comment">// map[a:100]</span></span><br><span class="line">t.Log(m1)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者传参也是类似：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMapArg</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">m := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">changeMap(m)</span><br><span class="line">t.Log(m)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeMap</span><span class="params">(m <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">m[<span class="string">&quot;x&quot;</span>] = <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是如果在函数里让参数指向一个新 map，不会影响外部变量本身：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMapArg</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">m := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">changeMap1(m)</span><br><span class="line">t.Log(m)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeMap1</span><span class="params">(m <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 新的map</span></span><br><span class="line">m = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">m[<span class="string">&quot;y&quot;</span>] = <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果想替换外部 map，需要返回新 map，或传 <code>*map[...]...</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 更推荐这种写法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">reset</span><span class="params">()</span></span> <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 只有在必须修改调用者持有的那个变量时，才考虑 *map[...]...</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">reset1</span><span class="params">(m *<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">*m = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其他关于<code>map线程安全</code>、<code>其他map常用api或编码技巧</code>等在后续学习中逐渐补充。</p><h2 id="struct-结构体"><a href="#struct-结构体" class="headerlink" title="struct(结构体)"></a>struct(结构体)</h2><p>struct 是 Go 中用于组合多个字段的数据类型，类似于Java中的classs。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">//同类型的可以合并写</span></span><br><span class="line">Id, Age <span class="type">int</span></span><br><span class="line">Name    <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStruct</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := User&#123;&#125;</span><br><span class="line">u.Age = <span class="number">18</span></span><br><span class="line">u.Name = <span class="string">&quot;John&quot;</span></span><br><span class="line">u.Id = <span class="number">1</span></span><br><span class="line">t.Log(u)</span><br><span class="line"></span><br><span class="line">u1 := User&#123;</span><br><span class="line">Id:   <span class="number">2</span>,</span><br><span class="line">Age:  <span class="number">25</span>,</span><br><span class="line">Name: <span class="string">&quot;Tom&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">t.Log(u1)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>struct 的零值是：所有字段都是各自类型的零值。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">//同类型的可以合并写</span></span><br><span class="line">Id, Age <span class="type">int</span></span><br><span class="line">Name    <span class="type">string</span></span><br><span class="line">Active  <span class="type">bool</span></span><br><span class="line">Tags    []<span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStruct</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u2 := User&#123;&#125;</span><br><span class="line">    <span class="comment">// &#123;0 0  false []&#125;</span></span><br><span class="line">t.Log(u2.Id)   <span class="comment">// 0</span></span><br><span class="line">t.Log(u2.Age)  <span class="comment">//0</span></span><br><span class="line">t.Log(u2.Name) <span class="comment">// “”</span></span><br><span class="line">t.Log(u2.Tags) <span class="comment">// []</span></span><br><span class="line">t.Log(u2.Active)<span class="comment">// false</span></span><br><span class="line">    t.Log(u2.Tags == <span class="literal">nil</span>) <span class="comment">// true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>struct 本身通常不为 <code>nil</code>，除非你使用的是 <code>*Struct</code> 指针。</p></div><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>对于<code>struct</code>，初始化方式有两种方式：</p><p><strong>按照字段名初始化（推荐）</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">u1 := User&#123;</span><br><span class="line">Id:   <span class="number">2</span>,</span><br><span class="line">Age:  <span class="number">25</span>,</span><br><span class="line">Name: <span class="string">&quot;Tom&quot;</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>日常编码应当使用这种方式进行初始化。</p><p><strong>按照字段顺序初始化</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">u2:= User&#123;<span class="number">3</span>,<span class="number">30</span>,<span class="string">&quot;Jerry&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>这种方式似乎看起来简单，但可读性差，一旦后期修改了struct的字段顺序，这样的代码就会出现错位或者报错。所以日常开发尽量不要使用这种方式。</p><p>个人觉得似乎还有一种，不过我是以Java程序员的视角似乎也是一种初始化，回头查查这样的初始化是不是有啥坑。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">u := User&#123;&#125;</span><br><span class="line">u.Age = <span class="number">18</span></span><br><span class="line">u.Name = <span class="string">&quot;John&quot;</span></span><br><span class="line">u.Id = <span class="number">1</span></span><br><span class="line">t.Log(u)</span><br></pre></td></tr></table></figure><h3 id="访问和修改字段"><a href="#访问和修改字段" class="headerlink" title="访问和修改字段"></a>访问和修改字段</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStructRU</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := User&#123;Id: <span class="number">1</span>, Name: <span class="string">&quot;zhangsan&quot;</span>, Active: <span class="literal">true</span>, Tags: []<span class="type">string</span>&#123;<span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Go&quot;</span>&#125;, Age: <span class="number">28</span>&#125;</span><br><span class="line">t.Log(u.Tags)</span><br><span class="line"></span><br><span class="line">u.Age = <span class="number">32</span></span><br><span class="line">t.Log(u.Age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="struct是值类型"><a href="#struct是值类型" class="headerlink" title="struct是值类型"></a>struct是值类型</h3><div class="note info simple"><p>struct 赋值时会拷贝整个值，不像 map、slice 那样共享整体结构。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStructType</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := User&#123;Id: <span class="number">1</span>, Name: <span class="string">&quot;zhangsan&quot;</span>, Active: <span class="literal">true</span>, Tags: []<span class="type">string</span>&#123;<span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Go&quot;</span>&#125;, Age: <span class="number">28</span>&#125;</span><br><span class="line">u1 := u</span><br><span class="line">u1.Name = <span class="string">&quot;Jack&quot;</span></span><br><span class="line">t.Log(u.Name)</span><br><span class="line">t.Log(u1.Name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，struct中的字段如果是<code>引用类型</code>，那么字段内部引用的数据仍然可能是共享的。</p><h3 id="拷贝"><a href="#拷贝" class="headerlink" title="拷贝"></a>拷贝</h3><p><strong>如果struct中包含slice&#x2F;map&#x2F;pointer时，拷贝是浅拷贝</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Person <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">Tags []<span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStructCopy</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">p1 := Person&#123;</span><br><span class="line">Name: <span class="string">&quot;Jack&quot;</span>,</span><br><span class="line">Tags: []<span class="type">string</span>&#123;<span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Go&quot;</span>&#125;,</span><br><span class="line">&#125;</span><br><span class="line">p2 := p1</span><br><span class="line">p2.Tags[<span class="number">0</span>] = <span class="string">&quot;Python&quot;</span></span><br><span class="line">    <span class="comment">// [Python Go]</span></span><br><span class="line">t.Log(p1.Tags)</span><br><span class="line">    <span class="comment">// [Python Go]</span></span><br><span class="line">t.Log(p2.Tags)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>原因：</p><ul><li>struct 本身被拷贝了</li><li>但 <code>Tags</code> 这个 slice 头部被拷贝</li><li>slice底层数组仍然共享</li></ul><p><strong>函数传struct默认也是值拷贝</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Person <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">Tags []<span class="type">string</span></span><br><span class="line">Age  <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">grow</span><span class="params">(p Person)</span></span> &#123;</span><br><span class="line">p.Age++</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStructCopyArg</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">p1 := Person&#123;</span><br><span class="line">Name: <span class="string">&quot;Jack&quot;</span>,</span><br><span class="line">Tags: []<span class="type">string</span>&#123;<span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Go&quot;</span>&#125;,</span><br><span class="line">Age:  <span class="number">28</span>,</span><br><span class="line">&#125;</span><br><span class="line">grow(p1)</span><br><span class="line">    <span class="comment">// 28</span></span><br><span class="line">t.Log(p1.Age)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果想要修改原来的对象，那么就需要传指针</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">grow1</span><span class="params">(p *Person)</span></span> &#123;</span><br><span class="line">p.Age++</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStructCopyArg</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">p1 := Person&#123;</span><br><span class="line">Name: <span class="string">&quot;Jack&quot;</span>,</span><br><span class="line">Tags: []<span class="type">string</span>&#123;<span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Go&quot;</span>&#125;,</span><br><span class="line">Age:  <span class="number">28</span>,</span><br><span class="line">&#125;</span><br><span class="line">grow1(&amp;p1)</span><br><span class="line">    <span class="comment">// 29</span></span><br><span class="line">t.Log(p1.Age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="struct-指针访问字段可以省略解引用"><a href="#struct-指针访问字段可以省略解引用" class="headerlink" title="struct 指针访问字段可以省略解引用"></a>struct 指针访问字段可以省略解引用</h3><div class="note info simple"><p>Go 允许自动解引用。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStruct2</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">p := &amp;Person&#123;Name: <span class="string">&quot;Jack&quot;</span>, Tags: []<span class="type">string</span>&#123;<span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Go&quot;</span>&#125;, Age: <span class="number">28</span>&#125;</span><br><span class="line">t.Log(p.Name) <span class="comment">// 等价于(*p).Name</span></span><br><span class="line">p.Age = <span class="number">20</span></span><br><span class="line">t.Log(p.Age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="如何判断使用struct还是-struct"><a href="#如何判断使用struct还是-struct" class="headerlink" title="如何判断使用struct还是*struct"></a>如何判断使用struct还是*struct</h3><p><strong>用 struct 值的情况</strong></p><p>适合：</p><ul><li><p>数据很小</p></li><li><p>不需要修改原对象</p></li><li><p>想避免共享状态</p></li><li><p>临时值、配置值、小对象</p></li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Point <span class="keyword">struct</span> &#123;</span><br><span class="line">    X, Y <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Move</span><span class="params">(p Point, dx, dy <span class="type">int</span>)</span></span> Point &#123;</span><br><span class="line">    p.X += dx</span><br><span class="line">    p.Y += dy</span><br><span class="line">    <span class="keyword">return</span> p</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>用 *struct 指针的情况</strong></p><p>适合：</p><ul><li><p><strong>struct 较大</strong></p></li><li><p><strong>需要修改原对象</strong></p></li><li><p><strong>避免频繁拷贝</strong></p></li><li><p><strong>方法需要改变 receiver（相当于Java的this）</strong></p></li><li><p><strong>包含锁、缓存、连接等状态</strong></p></li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Counter <span class="keyword">struct</span> &#123;</span><br><span class="line">    N <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *Counter)</span></span> Inc() &#123;</span><br><span class="line">    c.N++</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    c := &amp;Counter&#123;&#125;</span><br><span class="line">    c.Inc()</span><br><span class="line">    c.Inc()</span><br><span class="line"></span><br><span class="line">    fmt.Println(c.N) <span class="comment">// 2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><div class="note info simple"><p>Go 没有 class，但可以给类型定义方法。</p></div><p>参考上面的例子就可以说明问题。<code>Inc()</code>方法可以被<code>Counter</code>调用。</p><div class="note info simple"><p>receiver 选择原则：如果一个类型的方法中，有任意一个方法需要指针 receiver，通常所有方法都用指针 receiver，保持一致。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">    Name <span class="type">string</span></span><br><span class="line">    Age <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(u *User)</span></span> Rename(name <span class="type">string</span>)&#123;</span><br><span class="line">    u.Name = name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(u *User)</span></span> IsAdult() <span class="type">bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> u.Age &gt;= <span class="number">18</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h2><p>指针保存的是一个变量的内存地址。其语法如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> p *<span class="type">int</span></span><br></pre></td></tr></table></figure><ul><li><code>p</code> 是一个指针变量</li><li>它指向一个 <code>int</code> 类型的变量</li><li><code>*int</code> 表示“指向 int 的指针类型”</li></ul><h3 id="取地址"><a href="#取地址" class="headerlink" title="取地址"></a>取地址</h3><p>在Go语言中，使用<code>&amp;</code>获取变量地址。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGetAddress</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">10</span></span><br><span class="line"><span class="comment">// x 的内存地址，例如 0xc0000120a</span></span><br><span class="line">p := &amp;x</span><br><span class="line"><span class="comment">// x = 10, p = 44087471899344</span></span><br><span class="line">t.Logf(<span class="string">&quot;x = %d, p = %d&quot;</span>, x, p)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解引用"><a href="#解引用" class="headerlink" title="解引用"></a>解引用</h3><p>以上一个例子为例，使用<code>*p</code>访问指针指向的值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestDeref</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">10</span></span><br><span class="line">p := &amp;x</span><br><span class="line"><span class="comment">// *p = 10</span></span><br><span class="line">t.Logf(<span class="string">&quot;*p = %v&quot;</span>, *p)</span><br><span class="line">    <span class="comment">// 表示修改 p 指向的变量的值。</span></span><br><span class="line">*p = <span class="number">20</span></span><br><span class="line"><span class="comment">// x = 20, *p = 20</span></span><br><span class="line">t.Logf(<span class="string">&quot;x = %d, *p = %d&quot;</span>, x, *p)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="指针的零值"><a href="#指针的零值" class="headerlink" title="指针的零值"></a>指针的零值</h3><div class="note info simple"><p>指针类型的零值是 <code>nil</code>。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestZeroValue</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> p *<span class="type">int</span></span><br><span class="line"><span class="comment">//p == nil 的值是 true</span></span><br><span class="line">t.Logf(<span class="string">&quot;p == nil 的值是 %v&quot;</span>, p == <span class="literal">nil</span>)</span><br><span class="line">    <span class="comment">// 潜在的 nil 解引用</span></span><br><span class="line">    <span class="comment">// 第 26 行: &#x27;p == nil&#x27; 被假定等于 &#x27;nil&#x27;  </span></span><br><span class="line">    <span class="comment">// 第 27 行: &#x27;p&#x27; 被解引用 </span></span><br><span class="line">    <span class="comment">// t.Log(*p)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note warn simple"><p>注意：不能解引用<code>nil</code> 指针。</p></div><p>通常为了避免这个问题，使用如下写法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestZeroValue1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 假设任意一个指针</span></span><br><span class="line">x := <span class="number">10</span></span><br><span class="line">p := &amp;x</span><br><span class="line"><span class="keyword">if</span> p != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 执行正常的逻辑</span></span><br><span class="line">t.Log(*p)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;p is nil&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数传值和传指针"><a href="#函数传值和传指针" class="headerlink" title="函数传值和传指针"></a>函数传值和传指针</h3><p>其实前面的学习中，已经使用过几次。这里再介绍一下。</p><div class="note info simple"><p>Go 默认是值传递。</p><p>如果传普通变量，函数内部修改的是副本。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">change</span><span class="params">(x <span class="type">int</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">// 如果传普通变量，函数内部修改的是副本。</span></span><br><span class="line">x = <span class="number">20</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestArgsValue</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">n := <span class="number">10</span></span><br><span class="line">change(n)</span><br><span class="line">    <span class="comment">// n = 10</span></span><br><span class="line">t.Logf(<span class="string">&quot;n = %d&quot;</span>, n)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果想让函数修改外部变量，需要传指针。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changePoint</span><span class="params">(p *<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">*p = <span class="number">20</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestArgsPoint</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">n := <span class="number">10</span></span><br><span class="line">changePoint(&amp;n)</span><br><span class="line">    <span class="comment">// n = 20</span></span><br><span class="line">t.Logf(<span class="string">&quot;n = %d&quot;</span>, n)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="struct指针（重点）"><a href="#struct指针（重点）" class="headerlink" title="struct指针（重点）"></a>struct指针（重点）</h3><p>如果要修改struct的值，需要传递指针。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UserInfo <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">Age  <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法里的指针 receiver，通常修改struct时，需要这么定义。如果传值，那么修改不会影响元对象。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">birthday</span><span class="params">(u *UserInfo)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 等价于 (*u).Age++，Go 会自动解引用 struct 指针，所以一般不用写 (*u).Age。</span></span><br><span class="line">u.Age++</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestStructPointer</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">u := UserInfo&#123;</span><br><span class="line">Name: <span class="string">&quot;Jerry&quot;</span>,</span><br><span class="line">Age:  <span class="number">18</span>,</span><br><span class="line">&#125;</span><br><span class="line">birthday(&amp;u)</span><br><span class="line"><span class="comment">// Name = Jerry Age = 19</span></span><br><span class="line">t.Logf(<span class="string">&quot;Name = %v Age = %d&quot;</span>, u.Name, u.Age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="new函数"><a href="#new函数" class="headerlink" title="new函数"></a>new函数</h3><p>new(T)会分配一个T类型的值，并返回*T</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestNew</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">p := <span class="built_in">new</span>(<span class="type">int</span>)</span><br><span class="line">t.Logf(<span class="string">&quot;p = %v, *p = %v&quot;</span>, p, *p)</span><br><span class="line">*p = <span class="number">10</span></span><br><span class="line">t.Logf(<span class="string">&quot; *p = %v&quot;</span>, *p)</span><br><span class="line"><span class="comment">// 对于struct,以下方式等价于u := &amp;User&#123;&#125;，后者更常用</span></span><br><span class="line">u := <span class="built_in">new</span>(UserInfo)</span><br><span class="line">u.Name = <span class="string">&quot;Tom&quot;</span></span><br><span class="line">u.Age = <span class="number">18</span></span><br><span class="line">t.Logf(<span class="string">&quot;Name = %v Age = %d&quot;</span>, u.Name, u.Age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="指针不能做算数运算"><a href="#指针不能做算数运算" class="headerlink" title="指针不能做算数运算"></a>指针不能做算数运算</h3><div class="note info simple"><p>Go 指针不能像 C&#x2F;C++ 那样做指针偏移。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestPointCompute</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">x := <span class="number">10</span></span><br><span class="line">p := &amp;x</span><br><span class="line"><span class="comment">// 无效运算: p++(非数值类型 *int)</span></span><br><span class="line"><span class="comment">// p++</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同样也不支持下列操作：</span></span><br><span class="line"><span class="comment">//p + 1</span></span><br><span class="line"><span class="comment">//p--</span></span><br></pre></td></tr></table></figure><h3 id="指针和（数组、slice、map）"><a href="#指针和（数组、slice、map）" class="headerlink" title="指针和（数组、slice、map）"></a>指针和（数组、slice、map）</h3><div class="note info simple"><p>数组是值类型，传参会拷贝整个数组。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 传数组：会拷贝</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeArray</span><span class="params">(a [3]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">a[<span class="number">0</span>] = <span class="number">99</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 传数组指针：可以修改原数组</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">chageArrayPoint</span><span class="params">(a *[3]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">a[<span class="number">0</span>] = <span class="number">99</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestArray</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">arr := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">changeArray(arr)</span><br><span class="line">    <span class="comment">// arr = [1 2 3]</span></span><br><span class="line">t.Logf(<span class="string">&quot;arr = %v&quot;</span>, arr)</span><br><span class="line"></span><br><span class="line">chageArrayPoint(&amp;arr)</span><br><span class="line">    <span class="comment">// arr = [99 2 3]</span></span><br><span class="line">t.Logf(<span class="string">&quot;arr = %v&quot;</span>, arr)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>slice 本身包含指向底层数组的指针，所以传 slice 通常就能修改底层元素。</p></div><p>对于Go语言，slice的使用频率要远高于数组。下面通过一个例子来说明指针与切片：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeSlice</span><span class="params">(nums []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">nums[<span class="number">0</span>] = <span class="number">99</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeSliceAppend</span><span class="params">(nums []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">nums = <span class="built_in">append</span>(nums, <span class="number">4</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeSliceReturnNew</span><span class="params">(nums []<span class="type">int</span>)</span></span> []<span class="type">int</span> &#123;</span><br><span class="line">nums = <span class="built_in">append</span>(nums, <span class="number">5</span>)</span><br><span class="line"><span class="keyword">return</span> nums</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestSlice</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">nums := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="comment">// 修改第一值</span></span><br><span class="line">changeSlice(nums)</span><br><span class="line"><span class="comment">// 修改第一个值：nums = [99 2 3]</span></span><br><span class="line">t.Logf(<span class="string">&quot;修改第一个值：nums = %v&quot;</span>, nums)</span><br><span class="line">changeSliceAppend(nums)</span><br><span class="line"><span class="comment">//追加一个新值：nums = [99 2 3]</span></span><br><span class="line">t.Logf(<span class="string">&quot;追加一个新值：nums = %v&quot;</span>, nums)</span><br><span class="line">nums = changeSliceReturnNew(nums)</span><br><span class="line"><span class="comment">// 追加一个新值，并返回新的slice: nums = [99 2 3 5]</span></span><br><span class="line">t.Logf(<span class="string">&quot;追加一个新值，并返回新的slice: nums = %v&quot;</span>, nums)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>一般不推荐用 <code>*[]T</code>，除非你真的需要修改 slice 变量本身。</p></div><p>对于map来说，map 本身也是引用语义的数据结构，所以通常不需要传 <code>*map</code>。其他方式和slice基本一致。</p><h2 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h2><p>与Java的泛型类似，Go语言泛型允许在定义函数、类型或方法时，暂时不指定具体类型，而是在使用时再传入具体类型。</p><p>它的核心作用是：</p><ul><li><p>减少重复代码</p></li><li><p>提高类型安全</p></li><li><p>编写更通用的函数和数据结构</p></li></ul><p>下面是一个最直观的例子：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">maxInt</span><span class="params">(x <span class="type">int</span>, y <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">if</span> x &gt; y &#123;</span><br><span class="line"><span class="keyword">return</span> x</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">return</span> y</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">maxFloat</span><span class="params">(x <span class="type">float64</span>, y <span class="type">float64</span>)</span></span> <span class="type">float64</span> &#123;</span><br><span class="line"><span class="keyword">if</span> x &gt; y &#123;</span><br><span class="line"><span class="keyword">return</span> x</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">return</span> y</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用泛型</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Max</span>[<span class="title">T</span> <span class="title">int</span> | <span class="title">float64</span>]<span class="params">(a, b T)</span></span> T &#123;</span><br><span class="line"><span class="keyword">if</span> a &gt; b &#123;</span><br><span class="line"><span class="keyword">return</span> a</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">return</span> b</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGetMax</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// 不使用泛型</span></span><br><span class="line">t.Logf(<span class="string">&quot;maxInt(1, 2) = %d&quot;</span>, maxInt(<span class="number">1</span>, <span class="number">2</span>))</span><br><span class="line">t.Logf(<span class="string">&quot;maxFloat(1.1, 2.2) = %f&quot;</span>, maxFloat(<span class="number">1.1</span>, <span class="number">2.2</span>))</span><br><span class="line"><span class="comment">// 使用泛型</span></span><br><span class="line">t.Logf(<span class="string">&quot;Max[int](1, 2) = %d&quot;</span>, Max[<span class="type">int</span>](<span class="number">1</span>, <span class="number">2</span>))</span><br><span class="line">    t.Logf(<span class="string">&quot;Max[float64](1.1, 2.2) = %f&quot;</span>, Max[<span class="type">float64</span>](<span class="number">1.1</span>, <span class="number">2.2</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用泛型可以减少重复代码。</p><h3 id="泛型的语法格式"><a href="#泛型的语法格式" class="headerlink" title="泛型的语法格式"></a>泛型的语法格式</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> 函数名[<span class="title">T</span> 约束]<span class="params">(参数 T)</span></span> T</span><br></pre></td></tr></table></figure><ul><li>T是类型参数</li><li>约束限制T可以是什么类型</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Identity</span>[<span class="title">T</span> <span class="title">any</span>]<span class="params">(value T)</span></span> T &#123;</span><br><span class="line"><span class="keyword">return</span> value</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGenerics</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">t.Logf(<span class="string">&quot;Identity[int](1) = %d&quot;</span>, Identity[<span class="type">int</span>](<span class="number">1</span>))</span><br><span class="line">t.Logf(<span class="string">&quot;Identity[string](&#x27;hello&#x27;) = %v&quot;</span>, Identity[<span class="type">string</span>](<span class="string">&quot;Hello&quot;</span>))</span><br><span class="line"><span class="comment">// 也可以让Go取自动类型推导</span></span><br><span class="line">t.Logf(<span class="string">&quot;Identity(2) = %v&quot;</span>, Identity(<span class="number">2</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="any约束"><a href="#any约束" class="headerlink" title="any约束"></a>any约束</h3><div class="note info simple"><p><code>any</code> 是 Go 1.18 引入的内置类型别名，本质上等价于 <code>interface{}</code>。它表示可以接受任何类型。</p></div><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PrintValue</span>[<span class="title">T</span> <span class="title">any</span>]<span class="params">(value T)</span></span> &#123;</span><br><span class="line">fmt.Println(value)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestAny</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">PrintValue(<span class="number">123</span>)</span><br><span class="line">PrintValue(<span class="string">&quot;Hello World!&quot;</span>)</span><br><span class="line">PrintValue(<span class="literal">true</span>)</span><br><span class="line">PrintValue(<span class="number">3.1415</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="comparable约束"><a href="#comparable约束" class="headerlink" title="comparable约束"></a>comparable约束</h3><p><code>comparable</code> 表示该类型可以使用 <code>==</code> 和 <code>!=</code> 比较。</p><p>常见可比较类型：</p><ul><li><p>int</p></li><li><p>string</p></li><li><p>bool</p></li><li><p>指针</p></li><li><p>channel</p></li><li><p>由可比较字段组成的 struct</p></li></ul><p>不可比较类型：</p><ul><li><p>slice</p></li><li><p>map</p></li><li><p>function</p></li></ul><p>这一块和map的key比较像。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Equal</span>[<span class="title">T</span> <span class="title">comparable</span>]<span class="params">(a, b T)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> a == b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestComparable</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">t.Logf(<span class="string">&quot;Equal(1, 1) = %t&quot;</span>, Equal(<span class="number">1</span>, <span class="number">1</span>))</span><br><span class="line">t.Logf(<span class="string">&quot;Equal(&#x27;hello&#x27;, &#x27;hello&#x27;) = %t&quot;</span>, Equal(<span class="string">&quot;hello&quot;</span>, <span class="string">&quot;hello&quot;</span>))</span><br><span class="line">t.Logf(<span class="string">&quot;Equal(1.1, 1.1) = %t&quot;</span>, Equal(<span class="number">1.1</span>, <span class="number">1.1</span>))</span><br><span class="line"><span class="comment">// 下面的代码会报错，因为slice类型不支持比较</span></span><br><span class="line"><span class="comment">// t.Logf(&quot;Equal([]int&#123;1, 2&#125;, []int&#123;1, 2&#125;) = %t&quot;, Equal([]int&#123;1, 2&#125;, []int&#123;1, 2&#125;))</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自定义类型约束"><a href="#自定义类型约束" class="headerlink" title="自定义类型约束"></a>自定义类型约束</h3><p>可以通过接口定义自己的类型约束（<code>TypeScript</code>?）</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Number <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="type">int</span> | <span class="type">int64</span> | <span class="type">float64</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Add</span>[<span class="title">T</span> <span class="title">Number</span>]<span class="params">(a, b T)</span></span> T &#123;</span><br><span class="line"><span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestCustomType</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">num1 := Add(<span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line">num2 := Add(<span class="number">1.9</span>, <span class="number">2.1</span>)</span><br><span class="line">t.Logf(<span class="string">&quot;Add(2, 3) = %v&quot;</span>, num1)</span><br><span class="line">t.Logf(<span class="string">&quot;Add(1.9, 2.1) = %v&quot;</span>, num2)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>定义<code>Number</code>类型的<code>|</code>称之为类型联合，意思是<code>可以是这些类型中的任意一种</code>。例如如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Integer <span class="keyword">interface</span> &#123;</span><br><span class="line">    <span class="type">int</span> | <span class="type">int8</span> | <span class="type">int16</span> | <span class="type">int32</span> | <span class="type">int64</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>表示满足该约束的类型是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span></span><br><span class="line"><span class="type">int8</span></span><br><span class="line"><span class="type">int16</span></span><br><span class="line"><span class="type">int32</span></span><br><span class="line"><span class="type">int64</span></span><br></pre></td></tr></table></figure><p>这样的方式，也可以叫做类型集合。</p><h3 id="底层类型约束"><a href="#底层类型约束" class="headerlink" title="底层类型约束"></a>底层类型约束</h3><div class="note info simple"><p><code>~</code> 表示允许使用某个类型以及以该类型为底层类型的自定义类型。</p></div><p>下面通过两个例子作为对比：</p><p>不使用<code>~</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> MyInt <span class="type">int</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> IntOnly <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Double</span>[<span class="title">T</span> <span class="title">IntOnly</span>]<span class="params">(v T)</span></span> T &#123;</span><br><span class="line"><span class="keyword">return</span> v * <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用~</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> IntLike <span class="keyword">interface</span> &#123;</span><br><span class="line">~<span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">DoubleIntLike</span>[<span class="title">T</span> <span class="title">IntLike</span>]<span class="params">(v T)</span></span> T &#123;</span><br><span class="line"><span class="keyword">return</span> v * <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">//var x MyInt = 10</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">无法将 MyInt 用作类型 IntOnly</span></span><br><span class="line"><span class="comment">类型未实现约束 IntOnly，因为类型未包括在类型集(int)中</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="comment">//t.Logf(&quot;Double(x) = %v&quot;, Double(x))</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> y MyInt = <span class="number">20</span></span><br><span class="line">t.Logf(<span class="string">&quot;Double(y) = %v&quot;</span>, DoubleIntLike(y))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>~int</code> 的意思是： 只要底层类型是 int，都可以满足这个约束。</p></div><h3 id="泛型函数"><a href="#泛型函数" class="headerlink" title="泛型函数"></a>泛型函数</h3><p>泛型函数是最常见的泛型用法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Contains</span>[<span class="title">T</span> <span class="title">comparable</span>]<span class="params">(items []T, target T)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">for</span> _, item := <span class="keyword">range</span> items &#123;</span><br><span class="line"><span class="keyword">if</span> item == target &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGenericsFunc</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">t.Logf(<span class="string">&quot;Contains([]int&#123;1, 2, 3&#125;, 2) = %t&quot;</span>, Contains([]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;, <span class="number">2</span>))</span><br><span class="line">t.Logf(<span class="string">&quot;Contains([]string&#123;&#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;&#125;, &#x27;d&#x27;) = %t&quot;</span>, Contains([]<span class="type">string</span>&#123;<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>&#125;, <span class="string">&quot;d&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以实现一个类似lambda中的map操作。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Map</span>[<span class="title">T</span> <span class="title">any</span>, <span class="title">R</span> <span class="title">any</span>]<span class="params">(items []T, fn <span class="keyword">func</span>(T)</span></span> R) []R &#123;</span><br><span class="line">result := <span class="built_in">make</span>([]R, <span class="built_in">len</span>(items))</span><br><span class="line"><span class="keyword">for</span> i, item := <span class="keyword">range</span> items &#123;</span><br><span class="line">result[i] = fn(item)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestMapFunc</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">nums := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"></span><br><span class="line">strs := Map(nums, <span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Sprintf(<span class="string">&quot;Number: %d&quot;</span>, n)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">t.Log(strs)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同理，实现一个<code>Filter</code>和<code>Reduce</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Filter</span>[<span class="title">T</span> <span class="title">any</span>]<span class="params">(items []T, fn <span class="keyword">func</span>(T)</span></span> <span class="type">bool</span>) []T &#123;</span><br><span class="line">result := <span class="built_in">make</span>([]T, <span class="number">0</span>, <span class="built_in">len</span>(items))</span><br><span class="line"><span class="keyword">for</span> _, item := <span class="keyword">range</span> items &#123;</span><br><span class="line"><span class="keyword">if</span> fn(item) &#123;</span><br><span class="line">result = <span class="built_in">append</span>(result, item)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Reduce</span>[<span class="title">T</span> <span class="title">any</span>, <span class="title">R</span> <span class="title">any</span>]<span class="params">(items []T, initial R, fn <span class="keyword">func</span>(R, T)</span></span> R) R &#123;</span><br><span class="line">result := initial</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, item := <span class="keyword">range</span> items &#123;</span><br><span class="line">result = fn(result, item)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLambdaFunc</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">nums := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line">evens := Filter(nums, <span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> n%<span class="number">2</span> == <span class="number">0</span></span><br><span class="line">&#125;)</span><br><span class="line">t.Log(evens)</span><br><span class="line"></span><br><span class="line">sum := Reduce(nums, <span class="number">0</span>, <span class="function"><span class="keyword">func</span><span class="params">(acc <span class="type">int</span>, n <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> acc + n</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">t.Logf(<span class="string">&quot;Sum of nums = %d&quot;</span>, sum)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="结构体泛型"><a href="#结构体泛型" class="headerlink" title="结构体泛型"></a>结构体泛型</h3><p>不仅函数可以使用泛型，结构体也可以使用泛型。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Box[T any] <span class="keyword">struct</span> &#123;</span><br><span class="line">value T</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Pair[K comparable, V any] <span class="keyword">struct</span> &#123;</span><br><span class="line">Key   K</span><br><span class="line">Value V</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGenericsStruct</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">intBox := Box[<span class="type">int</span>]&#123;value: <span class="number">28</span>&#125;</span><br><span class="line">stringBox := Box[<span class="type">string</span>]&#123;value: <span class="string">&quot;Hello Go&quot;</span>&#125;</span><br><span class="line">t.Logf(<span class="string">&quot;intBox = %v, stringBox = %v&quot;</span>, intBox.value, stringBox.value)</span><br><span class="line"></span><br><span class="line">p1 := Pair[<span class="type">string</span>, <span class="type">int</span>]&#123;</span><br><span class="line">Key:   <span class="string">&quot;age&quot;</span>,</span><br><span class="line">Value: <span class="number">28</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">p2 := Pair[<span class="type">string</span>, <span class="type">string</span>]&#123;</span><br><span class="line">Key:   <span class="string">&quot;Name&quot;</span>,</span><br><span class="line">Value: <span class="string">&quot;Jerry&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">t.Logf(<span class="string">&quot;p1 = &#123;Key: %v, Value: %v&#125;, p2 = &#123;Key: %v, Value: %v&#125;&quot;</span>, p1.Key, p1.Value, p2.Key, p2.Value)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="泛型与接口的区别"><a href="#泛型与接口的区别" class="headerlink" title="泛型与接口的区别"></a>泛型与接口的区别</h3><p>虽然自定义泛型使用的是<code>interface</code>，看起来像是接口，但和接口还有些区别。普通接口关注的是<code>行为</code>，泛型约束关注的是<code>类型集合</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Writer <span class="keyword">interface</span> &#123;</span><br><span class="line">    Write([]<span class="type">byte</span>) (<span class="type">int</span>,<span class="type">error</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只要实现<code>Write</code>方法，就满足接口。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Number <span class="keyword">interface</span>&#123;</span><br><span class="line">    <span class="type">int</span> | <span class="type">int32</span> | <span class="type">float64</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>表示类型必须属于这个类型集合，也可以同时约束类型和方法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> StringNumber <span class="keyword">interface</span> &#123;</span><br><span class="line">    ~<span class="type">int</span> | ~<span class="type">int64</span></span><br><span class="line">    String() <span class="type">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但这种约束要求类型：</p><ol><li>底层类型是 <code>int</code> 或 <code>int64</code></li><li>实现了 <code>String() string</code></li></ol>]]>
    </content>
    <id>https://dev.net.cn/learning-go-03/</id>
    <link href="https://dev.net.cn/learning-go-03/"/>
    <published>2026-04-02T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p><strong>本章知识点：</strong></p>
<ul>
<li>数组与切片</li>
<li>map（字典）</li>
<li>struct（结构体）</li>
<li>指针</li>
<li>泛型</li>
</ul>]]>
    </summary>
    <title>Go语言学习笔记（三）- 复合数据类型</title>
    <updated>2026-04-02T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<p>作为Java程序员，本章内容就走马观花的了解一下即可，这一章内容和其他语言没有太大的不同。</p><span id="more"></span><h2 id="本章学习的内容"><a href="#本章学习的内容" class="headerlink" title="本章学习的内容"></a>本章学习的内容</h2><ul><li>包申明、导入、main方法</li><li>变量声明（<code>var</code>和<code>:=</code>的区别）</li><li>基本类型：int、float、string、bool</li><li>常量与<code>iota</code></li><li>类型转换</li></ul><h2 id="main方法"><a href="#main方法" class="headerlink" title="main方法"></a>main方法</h2><p>通过一个<code>HelloWorld</code>的程序，说明第一小节内容：包申明、导入包、main方法。创建一个<code>main.go</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Print(<span class="string">&quot;Hello World&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>包申明</strong>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br></pre></td></tr></table></figure><p>在Go语言中，每一个源文件都必须以<code>package</code>开头，它的作用就是定义了该文件属于哪个<code>命名空间</code>。这个名称可以随意定义，但<code>main</code>这个名称很特殊：</p><ol><li>如果该文件的<code>package</code>是<code>main</code>，那么这个程序将会被编译成一个<code>可执行程序</code>。</li><li>对于不同的文件，也可以将包名定义为<code>main</code>，但是<code>main</code>方法只能有一个。</li></ol><p>例如在<code>main.go</code>的同一级目录，创建一个<code>utils.go</code>，并且也声明为<code>main</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">max</span><span class="params">(a, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">if</span> a &gt; b &#123;</span><br><span class="line"><span class="keyword">return</span> a</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> b</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样是可以的，但如果在这个文件里也定义一个<code>main</code>方法，作为项目整体运行的时候，就会报错。</p><div class="note danger modern"><p>错误: 软件包 hellogo 包含多个 main 函数<br>请考虑改用文件种类</p></div><p><img src="https://cdn.dev.net.cn/images/2026/04/learning-go-01-goland-run-error.webp!full" alt="learning-go-01-goland-run-error"></p><p>如果运行种类那里换成<code>目录</code>，会提示如下错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># hellogo</span></span><br><span class="line">.\utils.go:12:6: main redeclared <span class="keyword">in</span> this block</span><br><span class="line">.\main.go:9:6: other declaration of main</span><br></pre></td></tr></table></figure><p>如果你在终端执行<code>go run utils.go</code>是可以执行的，因为它执行的模式是<code>文件</code>。</p><p><img src="https://cdn.dev.net.cn/images/2026/04/learning-go-02-go-run-utils.webp!full" alt="learning-go-02-go-run-utils"></p><p><strong>导入包</strong>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br></pre></td></tr></table></figure><p>这就和<code>Java</code>差不多，主要的区别就是不用的包不允许导入。</p><ul><li><p>包名必须使用双引号<code>”“</code>包裹。</p></li><li><p>导入的包必须使用，否则编译器会报错</p></li><li><p>如果有多个包，通常使用圆括号</p></li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">&quot;fmt&quot;</span></span><br><span class="line">  <span class="string">&quot;math&quot;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>main方法</strong>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和<code>Java</code>的<code>public static void main(String[] args)</code>一样，是程序的入口。</p><ul><li><code>func</code>关键字：用于定义方法（函数）</li><li>main方法不接受任何参数，也不返回任何值。</li><li>花括号必须跟在 <code>main()</code>后面， 如果使用<code>c#</code>那种换行会报错。</li></ul><h2 id="变量申明"><a href="#变量申明" class="headerlink" title="变量申明"></a>变量申明</h2><p>在<code>Go</code>语言中，通常变量申明有两种方式：第一种是<code>var</code>，另一种是简洁的申明方式<code>:=</code>，二者略有区别。</p><p><strong>单个变量申明</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> global = <span class="string">&quot;全局变量&quot;</span></span><br><span class="line"><span class="comment">//globalval :=100 不能作为全局变量</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// 声明一个变量</span></span><br><span class="line">    <span class="keyword">var</span> a1 = <span class="string">&quot;张三&quot;</span></span><br><span class="line">    fmt.Print(a1)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> username <span class="type">string</span></span><br><span class="line">    username = <span class="string">&quot;zhangsan&quot;</span></span><br><span class="line">    </span><br><span class="line">    a2:= <span class="string">&quot;李四&quot;</span>  <span class="comment">// 只能申明局部变量</span></span><br><span class="line">    fmt.Println(a2)</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure><p>和早期（<code>JDK11以前</code>）的<code>Java</code>不同，Go语言的类型时自己推断出来的，通常不需要显示的申明变量类型。</p><p>上述例子，分别体现了三种声明变量的方式：</p><ul><li><strong>类型推导</strong>：编译器根据等号右边的值自动判断<code>a1</code>的类型是<code>string</code>，一旦确定，那么<code>a1</code>的类型就会固定，不能再修改为其他类型。</li><li><strong>标准声明(var + 类型)</strong>：会和Java一样，有一个默认值的机制：如果你只声明 <code>var a int</code> 而不赋值，Go 会自动将其初始化为 <code>0</code>（字符串是 <code>&quot;&quot;</code>，布尔是 <code>false</code>）。</li><li><strong>短变量申明<code>:=</code></strong>：偷懒专用，但只能使用在方法（函数）内部，不能用于包级变量，且必须至少有一个变量是<strong>新声明</strong>的。</li></ul><p><strong>批量变量申明：</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;    </span><br><span class="line">    <span class="keyword">var</span> name1, name2 = <span class="string">&quot;李四&quot;</span>, <span class="number">22</span></span><br><span class="line">fmt.Println(name1, name2)</span><br><span class="line">    <span class="comment">// 声明时就赋值</span></span><br><span class="line">    <span class="keyword">var</span> (</span><br><span class="line">age1     = <span class="number">18</span></span><br><span class="line">age2     = <span class="number">20</span></span><br><span class="line">nickname = <span class="string">&quot;王五&quot;</span></span><br><span class="line">)</span><br><span class="line">fmt.Println(age1, age2, nickname)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 声明后赋值</span></span><br><span class="line">    <span class="keyword">var</span> (</span><br><span class="line">username11 <span class="type">string</span></span><br><span class="line">age11      <span class="type">int</span></span><br><span class="line">sex11      <span class="type">string</span></span><br><span class="line">)</span><br><span class="line">username11 = <span class="string">&quot;zhangsan&quot;</span></span><br><span class="line">age11 = <span class="number">11</span></span><br><span class="line">sex11 = <span class="string">&quot;男&quot;</span></span><br><span class="line">fmt.Println(username11, age11, sex11)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用:=申明多个变量</span></span><br><span class="line">    a, b, c := <span class="number">1</span>, <span class="number">2</span>, <span class="string">&quot;C&quot;</span></span><br><span class="line">fmt.Println(a, b, c)</span><br><span class="line">    <span class="comment">// 打印类型</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;%T %T&quot;</span>, a, c)</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure><p>对于批量变量申明，只需要<code>var(多个变量)</code>即可，非常优雅。非常适合归类，对于同一类的变量可以申明到一起，还增加了可读性。</p><p><strong>匿名变量</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">var</span> username, age = getUserInfo()</span><br><span class="line">    fmt.Println(username, age)</span><br><span class="line">    <span class="comment">//匿名变量</span></span><br><span class="line">    <span class="keyword">var</span> username1, _ = getUserInfo()</span><br><span class="line">    fmt.Println(username1)</span><br><span class="line">&#125; </span><br><span class="line"><span class="comment">// 定义一个方法</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getUserInfo</span><span class="params">()</span></span> (<span class="type">string</span>, <span class="type">int</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="string">&quot;zhangsan&quot;</span>, <span class="number">18</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果某个方法返回的值不需要，为了避免代码报错，那么可以使用下划线<code>_</code>作为匿名变量，这也是比较常用的方式。</p><div class="note warning modern"><p>和导入包一样，<code>未使用</code>的变量也会报错，不用变量的请及时清理。（谁懂读别人Java代码的苦啊）</p></div><h2 id="基本类型"><a href="#基本类型" class="headerlink" title="基本类型"></a>基本类型</h2><p>Go语言的基本类型有：</p><h3 id="整数类型"><a href="#整数类型" class="headerlink" title="整数类型"></a>整数类型</h3><ul><li>平台无关(需要明确长度的)<ul><li>有符号：<code>int8</code>、<code>int16</code>、<code>int32</code>、<code>int64</code></li><li>无符号：<code>uint8</code>、<code>uint16</code>、<code>uint32</code>、<code>uint64</code></li><li>特殊别名：<ul><li><code>byte</code>：等用于<code>uint8</code>，常用于处理二进制原始数据。</li><li><code>rune</code>：等同于<code>int32</code>，专用用于表示一个<code>Unicode</code>字符(Java里的<code>char</code>？)</li></ul></li></ul></li><li>平台相关<ul><li><code>int</code>&#x2F;<code>uint</code>：在 32 位系统上占 4 字节，在 64 位系统上占 8 字节（ Debian 13，<code>int</code> 默认就是 64 位）。</li><li><code>uintptr</code>：无符号整数，大小足以存放一个指针的位模式，多用于底层 <code>unsafe</code> 编程。</li></ul></li></ul><div class="note warning modern"><p>在 Go 中，<code>int</code> 和 <code>int64</code> 被视为不同的类型。即使在 64 位系统上两者的长度相同，你也不能直接将 <code>int</code> 变量赋值给 <code>int64</code>，必须显式转换：<code>int64(myInt)</code>。</p></div><h3 id="浮点型"><a href="#浮点型" class="headerlink" title="浮点型"></a>浮点型</h3><ul><li><code>float32</code>：单精度浮点数（约7位有效数字）</li><li><code>float64</code>： 双精度浮点数（约15位有效数字），它是Go语言的类型推导的默认值。</li></ul><p>所有语言的通病，也是面试题位数不懂相同的就是<code>0.1 + 0.2</code>的问题，Go也有！对于Java有<code>BigDecimal</code>，对于Go同样有<code>shopspring/decimal</code>。</p><h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><p>字符串<code>string(小写)</code>，它是<mark class="hl-label red">不可变类型</mark>，一旦创建，字符串内部的字节序列就不允许修改。其次，它的默认编码是<code>UTF-8</code>，本质上上一个只读的字节切片（<code>[]byte</code>）。</p><p>有几个坑需要注意：</p><p><code>len()</code>返回的是字符串的字节长度，而非字符个数</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">s := <span class="string">&quot;Hello Go语言&quot;</span></span><br><span class="line"><span class="comment">//打印出的长度位14(中文每个占用3字节)</span></span><br><span class="line">t.Log(<span class="built_in">len</span>(s))</span><br></pre></td></tr></table></figure><p>当然，非要统计字符个数，请使用<code>unicode/utf8</code>这个包</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line"><span class="string">&quot;unicode/utf8&quot;</span>  <span class="comment">//要导入这个包</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">s := <span class="string">&quot;Hello Go语言&quot;</span></span><br><span class="line"><span class="comment">// 打印长度为10</span></span><br><span class="line">t.Log(utf8.RuneCountInString(s))</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="布尔类型"><a href="#布尔类型" class="headerlink" title="布尔类型"></a>布尔类型</h3><p>类型名为<code>bool</code>,默认值为<code>false</code>，取值只有两个：<code>true</code>或者<code>false</code>，不允许像<code>c</code>那样，将<code>0</code>当作<code>false</code>。</p><h2 id="常量与iota"><a href="#常量与iota" class="headerlink" title="常量与iota"></a>常量与<code>iota</code></h2><h3 id="常量"><a href="#常量" class="headerlink" title="常量"></a>常量</h3><p>Go语言的常量使用<code>const</code>定义，可以显式声明类型，也可以利用类型推导。和其他语言一样，编译时就确定了值，并且不可改变。</p><p>其规则如下：</p><ul><li>编译时确定：常量的赋值必须是一个在编译期就能确定的值（如字面量、算术运算、内置函数如 <code>len()</code>）。<strong>不能</strong>把一个函数的返回值赋值给常量。</li><li>批量申明</li><li>常量展开（跳过赋值）：在一组常量中，如果某一行没有写赋值，那么它会自动复用上一行的表达式。</li></ul><p>下面通过一个例子来说明</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//常量</span></span><br><span class="line"><span class="keyword">const</span> pi = <span class="number">3.14</span></span><br><span class="line"><span class="comment">//pi = 11 会报错</span></span><br><span class="line">fmt.Println(pi)</span><br><span class="line"><span class="comment">// 批量声明</span></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">A = <span class="string">&quot;a&quot;</span></span><br><span class="line">B = <span class="string">&quot;b&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">fmt.Println(A, B)</span><br><span class="line"></span><br><span class="line"><span class="comment">//常量展开</span></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">n1 = <span class="number">100</span></span><br><span class="line">n2</span><br><span class="line">n3</span><br><span class="line">n4</span><br><span class="line">)</span><br><span class="line"><span class="comment">// 都是100</span></span><br><span class="line">fmt.Println(n1, n2, n3, n4)</span><br></pre></td></tr></table></figure><h3 id="iota-常量计数器"><a href="#iota-常量计数器" class="headerlink" title="iota(常量计数器)"></a>iota(常量计数器)</h3><p><code>iota</code> 是 Go 语言的一个特殊常量，它被设计用来简化<strong>累加数字</strong>的定义。你可以把它理解为 <code>const</code> 块中的<strong>行索引</strong>。通常用来做<code>枚举</code>、<code>错误码</code>、<code>权限位</code>、<code>物理单位</code>、<code>状态机</code>等。</p><h3 id="iota-的基本行为"><a href="#iota-的基本行为" class="headerlink" title="iota 的基本行为"></a><code>iota</code> 的基本行为</h3><ul><li><code>iota</code>  在 <code>const</code> 关键字出现时将被重置为 <strong>0</strong>。</li><li>每新增一行常量声明，<code>iota</code> 就会<strong>自动累加 1</strong>。</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">  <span class="comment">// 这里还使用了常量展开，学以致用。</span></span><br><span class="line">  <span class="keyword">const</span> (</span><br><span class="line">Java = <span class="literal">iota</span></span><br><span class="line">Go</span><br><span class="line">Rust</span><br><span class="line">C</span><br><span class="line">CPP</span><br><span class="line">Python</span><br><span class="line">)</span><br><span class="line">   <span class="comment">// 0,1,2,3,4,5</span></span><br><span class="line">t.Log(Java, Go, Rust, C, CPP, Python)</span><br></pre></td></tr></table></figure><p>如果像跳过某个值，可以使用如下方式：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> (</span><br><span class="line">n1, n2 = <span class="literal">iota</span> + <span class="number">1</span>, <span class="literal">iota</span> + <span class="number">2</span></span><br><span class="line">n3, n4</span><br><span class="line">n5, n6</span><br><span class="line">)</span><br><span class="line">   <span class="comment">// 1 2 2 3 3 4</span></span><br><span class="line">t.Log(n1, n2, n3, n4, n5, n6)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">       <span class="comment">// 跳过0</span></span><br><span class="line">_ = <span class="literal">iota</span></span><br><span class="line">n7</span><br><span class="line">n8</span><br><span class="line">       <span class="comment">//跳过3</span></span><br><span class="line">_</span><br><span class="line">n9</span><br><span class="line">)</span><br><span class="line">   <span class="comment">// 1 2 4</span></span><br><span class="line">t.Log(n7, n8, n9)</span><br></pre></td></tr></table></figure><p><code>iota</code> 最强大的地方在于它不仅能代表数字，还能参与<strong>位运算</strong>。这在定义权限、状态位或文件单位时非常高效。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> (</span><br><span class="line">READABLE   = <span class="number">1</span> &lt;&lt; <span class="literal">iota</span> <span class="comment">// 1 &lt;&lt; 0 = 1 (二进制 001)</span></span><br><span class="line">WRITEABLE              <span class="comment">// 1 &lt;&lt; 1 = 2 (二进制 010)</span></span><br><span class="line">EXECUTABLE             <span class="comment">// 1 &lt;&lt; 2 = 4 (二进制 100)</span></span><br><span class="line">)</span><br><span class="line">   t.Log(READABLE, WRITEABLE, EXECUTABLE)</span><br></pre></td></tr></table></figure><p>还有存储单位：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> (</span><br><span class="line">_  = <span class="literal">iota</span></span><br><span class="line">KB = <span class="number">1</span> &lt;&lt; (<span class="literal">iota</span> * <span class="number">10</span>) <span class="comment">// 1 &lt;&lt; (10*1) = 1024</span></span><br><span class="line">MB = <span class="number">1</span> &lt;&lt; (<span class="literal">iota</span> * <span class="number">10</span>) <span class="comment">// 1 &lt;&lt; (10*2) = 1048576</span></span><br><span class="line">GB = <span class="number">1</span> &lt;&lt; (<span class="literal">iota</span> * <span class="number">10</span>) <span class="comment">// 1 &lt;&lt; (10*2) = 1073741824</span></span><br><span class="line">TB = <span class="number">1</span> &lt;&lt; (<span class="literal">iota</span> * <span class="number">10</span>) <span class="comment">// 1 &lt;&lt; (10*2) = 1099511627776</span></span><br><span class="line">)</span><br><span class="line">t.Log(KB, MB, GB, TB)</span><br></pre></td></tr></table></figure><p>对于整形和浮点型，还有一个类型的问题：</p><ul><li>无类型常量： <code>const x = 123</code>，它们具有“高精度”且没有固定类型，可以根据上下文自动转换。</li><li>类型化常量：如 <code>const x int32 = 123</code>。它的类型是死板的，只能与 <code>int32</code> 运算。</li></ul><h2 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h2><p>Go 语言的类型转换遵循一个核心原则：<strong>显式大于隐式</strong>。它不支持像<code>Java</code>那样的括号强转<code>(int) a</code>，并且只有两种类型相互兼容时，才能转换（数值类型、或者对象底层结构相同）。</p><p><strong>语法T(v)</strong></p><p>Go 的类型转换非常直观，语法类似于函数调用：将变量 <code>v</code> 转换为类型 <code>T</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 直接用testing了，省得互相干扰</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_Convert</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> a <span class="type">int</span> = <span class="number">18</span></span><br><span class="line"><span class="keyword">var</span> b <span class="type">float64</span> = <span class="type">float64</span>(a)</span><br><span class="line"><span class="keyword">var</span> c <span class="type">uint</span> = <span class="type">uint</span>(b)</span><br><span class="line">t.Log(a, b, c)</span><br><span class="line">t.Logf(<span class="string">&quot;%T,%T,%T&quot;</span>, a, b, c)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">main_test.<span class="keyword">go</span>:<span class="number">80</span>: <span class="number">18</span> <span class="number">18</span> <span class="number">18</span></span><br><span class="line">main_test.<span class="keyword">go</span>:<span class="number">81</span>: <span class="type">int</span>,<span class="type">float64</span>,<span class="type">uint</span></span><br></pre></td></tr></table></figure><div class="note warning modern"><p>数值转换时，开发者必须亲自处理<strong>溢出</strong>和<strong>精度丢失</strong>的问题。</p></div><p>从浮点数转为整数时，小数部分会被<strong>直接截断</strong>，而不是四舍五入。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">f := <span class="number">3.9889</span></span><br><span class="line">i := <span class="type">int</span>(f)</span><br><span class="line"><span class="comment">// 3.9889 3</span></span><br><span class="line">t.Log(f, i)</span><br></pre></td></tr></table></figure><p>如果将大范围类型转换为小范围类型时，会发生截断。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> d <span class="type">int64</span> = <span class="number">257</span></span><br><span class="line"><span class="keyword">var</span> e <span class="type">int8</span> = <span class="type">int8</span>(d)</span><br><span class="line"><span class="comment">// 257 1</span></span><br><span class="line">t.Log(d, e)</span><br></pre></td></tr></table></figure><p>还有一个比较常见的场景，那就是字符串与切片的转换：</p><p><strong>字符串与字节切片(<code>[]byte</code>)</strong></p><p>Go 的字符串底层就是字节数组，转换效率很高，但会发生内存拷贝（除非使用 <code>unsafe</code> 包）。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_StringCovert</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;Hello Go语言&quot;</span></span><br><span class="line">b := []<span class="type">byte</span>(s)</span><br><span class="line">s2 := <span class="type">string</span>(b)</span><br><span class="line">    <span class="comment">// Hello Go语言 [72 101 108 108 111 32 71 111 232 175 173 232 168 128] Hello Go语言</span></span><br><span class="line">t.Log(s, b, s2)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>字符串与字符切片（<code>[]rune</code>）</strong></p><p>如果需要按字符（Unicode）处理中文，必须转为 <code>[]rune</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_StringCovert</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">s := <span class="string">&quot;Hello Go语言&quot;</span></span><br><span class="line">b1 := []<span class="type">rune</span>(s)</span><br><span class="line">s3 := <span class="type">string</span>(b1)</span><br><span class="line">    <span class="comment">// Hello Go语言 [72 101 108 108 111 32 71 111 35821 35328] Hello Go语言</span></span><br><span class="line">t.Log(s, b1, s3)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>字符串与数值的转换</strong></p><p>从这里开始，之前的语法<code>T(v)</code>就不好使了。需要使用<code>strconv</code>这个包</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;strconv&quot;</span></span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line"><span class="string">&quot;unicode/utf8&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Test_Str2Num</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">a1 := <span class="number">100</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// int -&gt; string</span></span><br><span class="line">s1 := strconv.Itoa(a1)</span><br><span class="line"><span class="comment">//100: int ,100: string</span></span><br><span class="line">t.Logf(<span class="string">&quot;%d: %T ,%s: %T&quot;</span>, a1, a1, s1, s1)</span><br><span class="line"><span class="comment">// string -&gt; int</span></span><br><span class="line">a2, _ := strconv.Atoi(s1)</span><br><span class="line"><span class="comment">// 100: string, 100: int</span></span><br><span class="line">t.Logf(<span class="string">&quot;%s: %T, %d: %T&quot;</span>, s1, s1, a2, a2)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 浮点数</span></span><br><span class="line">f1 := <span class="number">1.23554434344</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// float -&gt; string</span></span><br><span class="line"><span class="comment">// 需要指定精度和格式</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">第一个参数  float64</span></span><br><span class="line"><span class="comment">第二个参数  格式化类型：</span></span><br><span class="line"><span class="comment">   &#x27;f&#x27; 代表十进制 -ddd.dddd</span></span><br><span class="line"><span class="comment">&#x27;b&#x27; 代表二进制 -ddd.ddddp±ddd</span></span><br><span class="line"><span class="comment">&#x27;e&#x27;  代表科学计数法 -d.ddddde±ddd</span></span><br><span class="line"><span class="comment">&#x27;E&#x27;  代表科学计数法 -d.ddddE±ddd</span></span><br><span class="line"><span class="comment">&#x27;g&#x27;  代表十进制或科学计数法 -ddd.dddd</span></span><br><span class="line"><span class="comment">&#x27;G&#x27;  代表十进制或科学计数法 -ddd.ddddE±ddd</span></span><br><span class="line"><span class="comment">第三个参数  精度</span></span><br><span class="line"><span class="comment">第四个参数  精度范围 32 or 64</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">sf1 := strconv.FormatFloat(f1, <span class="string">&#x27;f&#x27;</span>, <span class="number">2</span>, <span class="number">64</span>)</span><br><span class="line"><span class="comment">// 1.235544: float64, 1.24: string</span></span><br><span class="line">t.Logf(<span class="string">&quot;%f: %T, %s: %T&quot;</span>, f1, f1, sf1, sf1)</span><br><span class="line"></span><br><span class="line"><span class="comment">// string -&gt; float</span></span><br><span class="line">f2, _ := strconv.ParseFloat(sf1, <span class="number">64</span>)</span><br><span class="line"><span class="comment">// 1.24: string, 1.240000: float64</span></span><br><span class="line">t.Logf(<span class="string">&quot;%s: %T, %f: %T&quot;</span>, sf1, sf1, f2, f2)</span><br><span class="line"></span><br><span class="line"><span class="comment">// bool</span></span><br><span class="line">flag := <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// bool -&gt; string</span></span><br><span class="line">s2 := strconv.FormatBool(flag)</span><br><span class="line"><span class="comment">//true: bool, true: string</span></span><br><span class="line">t.Logf(<span class="string">&quot;%t: %T, %s: %T&quot;</span>, flag, flag, s2, s2)</span><br><span class="line"></span><br><span class="line"><span class="comment">// string -&gt; bool</span></span><br><span class="line"></span><br><span class="line">b1, err := strconv.ParseBool(s2)</span><br><span class="line"><span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">//true: string, true: bool</span></span><br><span class="line">t.Logf(<span class="string">&quot;%s: %T, %t: %T&quot;</span>, s2, s2, b1, b1)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看样子除了<code>string&lt;-&gt;int</code>，其他的都是<code>ParseT</code>和<code>FormatT</code>，并且使用<code>ParseT</code>的时候，会返回多返回err，学习阶段使用匿名变量接收即可，实际开发中需要判断错误。</p>]]>
    </content>
    <id>https://dev.net.cn/learning-go-02/</id>
    <link href="https://dev.net.cn/learning-go-02/"/>
    <published>2026-04-01T03:00:00.000Z</published>
    <summary>
      <![CDATA[<p>作为Java程序员，本章内容就走马观花的了解一下即可，这一章内容和其他语言没有太大的不同。</p>]]>
    </summary>
    <title>Go语言学习笔记（二）- 基础语法与数据类型</title>
    <updated>2026-04-01T03:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Go" scheme="https://dev.net.cn/categories/Go/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>2017年，简单的学习了一下Go语言，但苦于当时的工作没有办法实践，而我又刚换了工作，每天都要加班到最后一班地铁，没多久，学习的内容就基本上忘光光了，今年得空，可以在学习下Go语言，手头上也有两个项目有时间去重构，那就重新学习一次吧。本次学习使用<code>go 1.26</code>版本。</p><h2 id="Go语言介绍"><a href="#Go语言介绍" class="headerlink" title="Go语言介绍"></a>Go语言介绍</h2><p>Go语言由Google开发，对于程序开发效率、并发、云原生等有非常好的支持。对于<code>Java</code>程序员来说，学习起来非常容易。</p><ul><li><strong>核心特性</strong>：天然并发（Goroutine）、强类型、编译速度极快、垃圾回收（GC）、静态链接成单个可执行二进制文件。</li><li><strong>适用场景</strong>：云原生开发（Docker&#x2F;K8s 均由 Go 编写）、微服务、系统工具、高性能网络服务。</li></ul><h2 id="GOROOT和GOPATH"><a href="#GOROOT和GOPATH" class="headerlink" title="GOROOT和GOPATH"></a>GOROOT和GOPATH</h2><p>对于Go新手还是有必要明确下这两个环境变量的含义，不然很容易纯靠字面意思搞错。</p><p><code>GOROOT</code>，就是<code>Go SDK</code>的安装路径，不是go语言的项目路径，对于<code>Java</code>程序员来说，就是<code>JDK</code>的路径。</p><p><code>GOPATH</code>，工作区路径，类似于<code>Eclipse+maven</code>的组合，将<code>workspace和.m2合并在一个目录</code>。最开始用起来老别扭了，不过从<code>Go 1.11</code>以后，就进入<code>Go Modules</code>时代。将<code>GOPATH</code>理解为<code>maven</code>的仓库，里面存放了不项目的不同依赖（以及同一个库的多个版本），<code>Go Modules</code>则负责隔离，每个项目可以存放在任意目录，并且根目录下有一个<code>go.mod</code>文件，记录该项目具体以来那个版本的库。</p><h2 id="安装配置Go"><a href="#安装配置Go" class="headerlink" title="安装配置Go"></a>安装配置Go</h2><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active">Windows</button><button type="button" class="tab">macOS</button><button type="button" class="tab">Debian</button></div><div class="tab-contents"><div class="tab-item-content active"><p>对于<code>Windows</code>，推荐使用<code>Scoop</code>来安装管理</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop install go</span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 对于使用brew的用户</span></span><br><span class="line">brew install go</span><br><span class="line"><span class="comment"># 对于使用port的用户</span></span><br><span class="line">port install go</span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget https://go.dev/dl/go1.26.1.linux-amd64.tar.gz</span><br><span class="line">tar -C /usr/local -xzf go1.26.1.linux-amd64.tar.gz</span><br></pre></td></tr></table></figure></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><p>安装完成后，执行<code>go version</code>，如果能正确输出go版本号，那就说明安装成功（以上三种方式都不需要单独设置环境变量，其他方式请自行配置）。下面配置<code>Go env</code>，以Windows为例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">GOROOT=D:\Scoop\apps\go\current</span><br><span class="line">GOPATH=D:\devtools\go</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启用go module</span></span><br><span class="line">go <span class="built_in">env</span> -w GO111MODULE=on</span><br><span class="line">go <span class="built_in">env</span> -w GOPROXY=https://goproxy.cn,direct</span><br></pre></td></tr></table></figure><div class="note warning modern"><p>除非你有特殊的目录洁癖，否则保持 <code>GOBIN</code> 为空，只需确保将 <code>$(go env GOPATH)/bin</code> 加入系统 <code>PATH</code> 即可。</p></div><p>对于新手最容易混淆的地方：</p><ul><li><strong><code>go build</code></strong>：在<strong>当前目录</strong>生成二进制文件。它不受 <code>GOBIN</code> 影响。</li><li><strong><code>go install</code></strong>：将二进制文件编译并移动到 <strong><code>GOBIN</code></strong>（或默认的 <code>bin</code>）目录下。</li></ul><p>配置完成后，执行<code>go env</code>查看是否正确。</p><h2 id="编辑器选择"><a href="#编辑器选择" class="headerlink" title="编辑器选择"></a>编辑器选择</h2><h3 id="VS-Code"><a href="#VS-Code" class="headerlink" title="VS Code"></a>VS Code</h3><p>对于喜欢轻量级的开发者来说，<code>VS Code</code>是最佳选择，使用<code>VS Code</code>开发Go，只需要安装<code>go</code>插件即可。安装完成后<code>Ctrl + Shift + P</code>，输入<code>Go: Install/Update Tools</code>，全选并安装，它会提供<code>自动补全</code>、<code>调试</code>等功能。按下<code>F5</code>即可调试程序，或者安装<code>Code Runner</code>插件，亦或者是在命令行中执行<code>go run .</code>。</p><h3 id="GoLand（推荐）"><a href="#GoLand（推荐）" class="headerlink" title="GoLand（推荐）"></a>GoLand（推荐）</h3><p>由于我是<code>Jetbrains</code>的全家桶套餐订阅者，所以我更偏向于<code>Goland</code>，用起来更顺手。</p>]]>
    </content>
    <id>https://dev.net.cn/learning-go-01/</id>
    <link href="https://dev.net.cn/learning-go-01/"/>
    <published>2026-04-01T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>2017年，简单的学习了一下Go语言，但苦于当时的工作没有办法实践，而我又刚换了工作，每天都要加班到最后一班地铁，没多久，学习的内容就基本上]]>
    </summary>
    <title>Go语言学习笔记（一）- 环境配置</title>
    <updated>2026-04-01T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Rust" scheme="https://dev.net.cn/categories/Rust/"/>
    <category term="Rust" scheme="https://dev.net.cn/tags/rust/"/>
    <content>
      <![CDATA[<p>从2024年简单的学习了下Rust后，就再也没怎么使用过，最近得闲，准备好好学习下<code>rust</code>。看了下之前记录的Rust笔记略显过时，倒不如从头再来，再学一遍！</p><span id="more"></span><h2 id="安装Rust"><a href="#安装Rust" class="headerlink" title="安装Rust"></a>安装Rust</h2><h3 id="下载rustup"><a href="#下载rustup" class="headerlink" title="下载rustup"></a>下载rustup</h3><p>和其他编程语言不同，安装<code>Rust</code>需要安装的是<code>rustup</code>，然后再利用<code>rustup</code>来安装<code>Rust</code>。这里就推荐使用官网的下载器来安装，我之前使用Scoop安装了rust，但是实际上它不过是个二进制的程序而已，并不满足rust开发。</p><p>打开官方文档<a href="https://rust-lang.org/zh-CN/tools/install/">安装 Rust - Rust 程序设计语言</a>，在这里下载<code>rustup-init.exe</code>，下载后先不着急安装，配置项环境变量。</p><h3 id="配置环境变量"><a href="#配置环境变量" class="headerlink" title="配置环境变量"></a>配置环境变量</h3><p>这一步需要配置四个变量，分别是<code>rustup</code>和<code>cargo</code>安装的路径，以及加速器地址。打开<code>设置</code> -&gt; <code>高级系统设置</code> -&gt; <code>环境变量</code>，在用户变量中设置如下四个环境变量</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装路径，cargo类似于pypi，rustup类似于rust安装器</span></span><br><span class="line">CARGO_HOME=D:\devtools\rust\cargo</span><br><span class="line">RUSTUP_HOME=D:\devtools\rust\rustup</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>配置字节加速器，不然慢的要死</strong></p><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active">中科大源(推荐)</button><button type="button" class="tab">清华大学源</button><button type="button" class="tab">字节跳动源</button></div><div class="tab-contents"><div class="tab-item-content active"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">RUSTUP_DIST_SERVER=<span class="string">&quot;https://mirrors.ustc.edu.cn/rust-static&quot;</span></span><br><span class="line">RUSTUP_UPDATE_ROOT=<span class="string">&quot;https://mirrors.ustc.edu.cn/rust-static/rustup&quot;</span></span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">RUSTUP_DIST_SERVER=<span class="string">&quot;https://mirrors.tuna.tsinghua.edu.cn/rustup&quot;</span></span><br><span class="line">RUSTUP_UPDATE_ROOT=<span class="string">&quot;https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup&quot;</span></span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">RUSTUP_DIST_SERVER=<span class="string">&quot;https://rsproxy.cn&quot;</span></span><br><span class="line">RUSTUP_UPDATE_ROOT=<span class="string">&quot;https://rsproxy.cn/rustup&quot;</span></span><br></pre></td></tr></table></figure></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>双击<code>rustup-init.exe</code>，会给你三个选项，<code> Proceed with standard installation</code>，<code>输入1</code>，然后安装即可。期间可能会弹出下载<code>vs buildtools</code>的界面。因为我以前就安装了，所以基本上都是在命令行中完成安装。</p><h3 id="配置源"><a href="#配置源" class="headerlink" title="配置源"></a>配置源</h3><p>安装完成后，还需要配置<code>crates-io</code>源。创建配置文件： <code>~/.cargo/config.toml</code>，然后填入如下内容：</p><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active">中科大源(推荐)</button><button type="button" class="tab">清华大学源</button><button type="button" class="tab">字节跳动源</button></div><div class="tab-contents"><div class="tab-item-content active"><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[source.crates-io]</span></span><br><span class="line"><span class="attr">replace-with</span> = <span class="string">&#x27;ustc&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[source.ustc]</span></span><br><span class="line"><span class="attr">registry</span> = <span class="string">&quot;sparse+https://mirrors.ustc.edu.cn/crates.io-index/&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[registries.ustc]</span></span><br><span class="line"><span class="attr">index</span> = <span class="string">&quot;sparse+https://mirrors.ustc.edu.cn/crates.io-index/&quot;</span></span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[source.crates-io]</span></span><br><span class="line"><span class="attr">replace-with</span> = <span class="string">&#x27;tuna&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[source.tuna]</span></span><br><span class="line"><span class="attr">registry</span> = <span class="string">&quot;sparse+https://mirrors.tuna.tsinghua.edu.cn/rust-lang/crates.io-index/&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[registries.tuna]</span></span><br><span class="line"><span class="comment"># 注意：确保这里的 URL 与上面的完全一致，且包含完整的稀疏协议前缀</span></span><br><span class="line"><span class="attr">index</span> = <span class="string">&quot;sparse+https://mirrors.tuna.tsinghua.edu.cn/rust-lang/crates.io-index/&quot;</span></span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[source.crates-io]</span></span><br><span class="line"><span class="attr">replace-with</span> = <span class="string">&#x27;rsproxy-sparse&#x27;</span></span><br><span class="line"><span class="section">[source.rsproxy]</span></span><br><span class="line"><span class="attr">registry</span> = <span class="string">&quot;https://rsproxy.cn/crates.io-index&quot;</span></span><br><span class="line"><span class="section">[source.rsproxy-sparse]</span></span><br><span class="line"><span class="attr">registry</span> = <span class="string">&quot;sparse+https://rsproxy.cn/index/&quot;</span></span><br><span class="line"><span class="section">[registries.rsproxy]</span></span><br><span class="line"><span class="attr">index</span> = <span class="string">&quot;https://rsproxy.cn/crates.io-index&quot;</span></span><br><span class="line"><span class="section">[net]</span></span><br><span class="line"><span class="attr">git-fetch-with-cli</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><p>添加完成后，测试</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo search serde --registry ustc（tuna） </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-rust-01/</id>
    <link href="https://dev.net.cn/learning-rust-01/"/>
    <published>2026-03-26T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>从2024年简单的学习了下Rust后，就再也没怎么使用过，最近得闲，准备好好学习下<code>rust</code>。看了下之前记录的Rust笔记略显过时，倒不如从头再来，再学一遍！</p>]]>
    </summary>
    <title>Rust学习笔记</title>
    <updated>2026-03-26T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="NodeJS" scheme="https://dev.net.cn/categories/NodeJS/"/>
    <category term="NodeJS" scheme="https://dev.net.cn/tags/nodejs/"/>
    <category term="NVM" scheme="https://dev.net.cn/tags/nvm/"/>
    <category term="FNM" scheme="https://dev.net.cn/tags/fnm/"/>
    <content>
      <![CDATA[<p> 今天用Claude调试以前的前端项目时，我需要使用<code>nodejs14</code>，按照往常的习惯直接使用<code>nvm install 14</code>，并且切换过去即可。但这次出问题了，我是用<code>nvm use 14</code>，虽然显示切换成功了，但使用<code>node -v</code>依旧显示的时<code>nodejs 24</code>的版本。后续折腾一番环境变量，终于可以切换了，但安装<code>nodejs 14</code>的时候又出幺蛾子了（实测nodejs 18就没问题），<code>npm</code>死活装不上，虽然可以通过手动下载等方式将其配置好，但也略嫌麻烦，于是就萌生了更换工具的想法。</p><span id="more"></span><p>对于<code>Nodejs</code>的版本管理工具，还有另外一个使用<code>Rust</code>实现的<code>fnm</code>。具体请查看<a href="https://github.com/Schniz/fnm">Schniz&#x2F;fnm: 🚀 Fast and simple Node.js version manager, built in Rust</a>。我目前使用scoop来管理这些开发软件，所以安装也是极其方便。（别忘了先卸载nvm）</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop install fnm</span><br></pre></td></tr></table></figure><p>安装完成后，其命令几乎和<code>nvm</code>一样一样的。</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装node</span></span><br><span class="line">fnm install <span class="number">24</span></span><br><span class="line">fnm install <span class="number">14</span></span><br><span class="line"><span class="comment"># 切换版本</span></span><br><span class="line">fnm use <span class="number">14</span></span><br><span class="line"><span class="comment"># 查看安装了那些</span></span><br><span class="line">fnm list</span><br><span class="line"><span class="comment"># 设置为默认版本</span></span><br><span class="line">fnm default <span class="number">24</span></span><br><span class="line"><span class="comment"># 卸载</span></span><br><span class="line">fnm uninstall <span class="number">18</span></span><br><span class="line"><span class="comment"># 查看当前使用的</span></span><br><span class="line">fnm current</span><br><span class="line"><span class="comment"># 设置别名</span></span><br><span class="line">fnm alias <span class="number">14.21</span>.<span class="number">3</span> project_name</span><br><span class="line"><span class="comment"># 使用别名</span></span><br><span class="line">fnm use project_name</span><br><span class="line"><span class="comment"># 删除别名</span></span><br><span class="line">fnm unalias project_name</span><br></pre></td></tr></table></figure><div class="note warning modern"><p>和nvm替换全局不同，fnm仅仅对当前session有效。</p></div><p>所以，安装完成后，一般会有一段提示，让你再powershell的<code>$PROFILE</code>末尾配置如下：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置 fnm 环境变量</span></span><br><span class="line">fnm env <span class="literal">--use-on-cd</span> | <span class="built_in">Out-String</span> | <span class="built_in">Invoke-Expression</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果是bash/zsh</span></span><br><span class="line">eval <span class="string">&quot;<span class="variable">$</span>(fnm env --use-on-cd)&quot;</span></span><br></pre></td></tr></table></figure><p>这个命令的作用就是进入目录后，如果当前目录存在<code>.node-version</code>（或者<code>.nvmrc</code>），并且里面是版本号时，就会自动切换到这个node版本，简直是健忘症的福音啊。所以我在每个前端的项目中，都创建了这个文件，并且指定版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在前端项目的根目录，请千万注意，不要把文件名搞成.node_version，我因为这个问题和AI讨论了一下午，AI都开始怀疑是PWS的问题了。</span></span><br><span class="line">nvim .node-version</span><br><span class="line"><span class="comment"># 填入一个版本号，例如：</span></span><br><span class="line">14</span><br></pre></td></tr></table></figure><p>这样，打开不同的前端项目就会自动的切换到指定版本。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/fnm_terminal.webp!full" alt="效果"></p><p>它的检测顺序是：</p><ol><li><code>.node-version</code>文件</li><li><code>.nvmrc</code>文件</li><li><code>package.json</code>中的<code>engines.node</code>字段（需启用<code>--resolve-engines</code>）</li></ol><p>当然，也可以零时使用某个版本来执行命令，并且不会影响已经使用其他版本运行的项目。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fnm <span class="built_in">exec</span> --using=14 node index.js</span><br></pre></td></tr></table></figure><p>这是 <code>fnm</code> 和 <code>nvm-windows</code> 最大的技术区别：</p><ul><li><strong>nvm</strong>：通过修改系统的全局 <code>Path</code> 和创建硬链接来工作，容易被其他软件抢占优先级。</li><li><strong>fnm</strong>：当你执行 <code>fnm use</code> 时，它只在当前的 PowerShell 会话（窗口）里临时修改 <code>Path</code>。</li><li><strong>意义</strong>：这意味着你可以在窗口 A 开着 Node 14 跑老代码，窗口 B 开着 Node 24 跑新代码，<strong>互不干扰</strong>。</li></ul>]]>
    </content>
    <id>https://dev.net.cn/switching-from-nvm-to-fnm-for-node-management/</id>
    <link href="https://dev.net.cn/switching-from-nvm-to-fnm-for-node-management/"/>
    <published>2026-03-21T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p> 今天用Claude调试以前的前端项目时，我需要使用<code>nodejs14</code>，按照往常的习惯直接使用<code>nvm install 14</code>，并且切换过去即可。但这次出问题了，我是用<code>nvm use 14</code>，虽然显示切换成功了，但使用<code>node -v</code>依旧显示的时<code>nodejs 24</code>的版本。后续折腾一番环境变量，终于可以切换了，但安装<code>nodejs 14</code>的时候又出幺蛾子了（实测nodejs 18就没问题），<code>npm</code>死活装不上，虽然可以通过手动下载等方式将其配置好，但也略嫌麻烦，于是就萌生了更换工具的想法。</p>]]>
    </summary>
    <title>放弃nvm改换fnm</title>
    <updated>2026-03-21T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="WSL" scheme="https://dev.net.cn/categories/WSL/"/>
    <category term="Linux" scheme="https://dev.net.cn/tags/linux/"/>
    <category term="Arch" scheme="https://dev.net.cn/tags/arch/"/>
    <category term="Fedora" scheme="https://dev.net.cn/tags/fedora/"/>
    <content>
      <![CDATA[<p>中午无聊，打算装两个Linux玩玩，我选择了<code>Fedora42</code>和<code>Arch</code>，这两个系统在<code>WSL</code>中式支持的，但默认安装在C盘，还得再导出导入一次才能换到其他盘，那何必多此一举，直接导入到其他盘即可。下面介绍下本次安装的过程。</p><span id="more"></span><h2 id="系统包准备"><a href="#系统包准备" class="headerlink" title="系统包准备"></a>系统包准备</h2><ul><li><p>Fedora42 ，<a href="https://dl.fedoraproject.org/pub/fedora/linux/releases/42/Container/x86_64/images/">Fedroa42 下载地址</a></p></li><li><p>ArchLinux，<a href="https://mirrors.edge.kernel.org/archlinux/iso/latest/">ArchLinux下载地址</a></p></li></ul><p>打开Fedora下载地址，<code>Fedora</code>官方支持WSL的镜像，所以只需要下载<code>Fedora-WSL-Base-42-1.1.x86_64.tar.xz</code>即可。</p><p>打开ArchLinux下载地址，Arch官方没有WSL的镜像，我们下载<code>archlinux-bootstrap-x86_64.tar.zst</code>，然后在任意一个Linux中使用<code>zstd</code>命令将其转换为<code>tar</code>包。以Ubuntu为例</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install zstd</span><br><span class="line"><span class="comment"># 解压 zst 并打包为 tar</span></span><br><span class="line">zstd -d archlinux-bootstrap-x86_64.tar.zst</span><br></pre></td></tr></table></figure><div class="note warning modern"><p>ArchLinux默认会比其他发行版多一层目录<code>root.x86_64</code>，所以这个包还需要再处理下才行，不能直接导入。</p></div><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建临时目录</span></span><br><span class="line"><span class="built_in">mkdir</span> temp_arch</span><br><span class="line"><span class="comment"># 解压到该目录</span></span><br><span class="line"><span class="built_in">sudo</span> tar -xvf archlinux-bootstrap-x86_64.tar -C temp_arch</span><br><span class="line"><span class="comment"># 重新打包</span></span><br><span class="line"><span class="built_in">cd</span> temp_arch/root.x86_64</span><br><span class="line"><span class="built_in">sudo</span> tar -cvf ../../arch_fixed.tar .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理临时文件</span></span><br><span class="line"><span class="built_in">cd</span> ../..</span><br><span class="line"><span class="built_in">rm</span> -rf temp_arch</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将其拷贝到Window硬盘中</span></span><br><span class="line"><span class="built_in">cp</span> arch_fixed.tar /mnt/e/Downloads</span><br></pre></td></tr></table></figure><h2 id="导入Linux系统"><a href="#导入Linux系统" class="headerlink" title="导入Linux系统"></a>导入Linux系统</h2><p>分别创建两个目录<code>E:\WSL\Fedora42</code>和<code>E:\WSL\Arch</code>，在PowerShell中执行如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> e:\WSL\Fedora42</span><br><span class="line"></span><br><span class="line"><span class="built_in">mkdir</span> e:\WSL\Arch</span><br></pre></td></tr></table></figure><p>导入系统</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 导入Fedora42</span></span><br><span class="line">wsl <span class="literal">--import</span> Fedora42 E:\WSL\Fedora42 E:\Downloads\Fedora<span class="literal">-WSL-Base-42-1</span>.<span class="number">1</span>.x86_64.tar.xz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导入ArchLinux</span></span><br><span class="line">wsl <span class="literal">--import</span> Arch E:\WSL\Arch E:\Downloads\arch_fixed.tar</span><br></pre></td></tr></table></figure><h2 id="启动系统"><a href="#启动系统" class="headerlink" title="启动系统"></a>启动系统</h2><p>使用wsl命令启动这两个系统，后面的参数就是wsl系统的名称，也就是上一步<code>--import</code>后面的那个名字。</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">-d</span> Fedora42</span><br><span class="line"></span><br><span class="line">wsl <span class="literal">-d</span> Arch</span><br></pre></td></tr></table></figure><p>默认是<code>root</code>启动，还需要再次配置。</p><h2 id="配置WSL系统"><a href="#配置WSL系统" class="headerlink" title="配置WSL系统"></a>配置WSL系统</h2><p>接着就是对系统的初始化配置了</p><h3 id="配置Fedora42"><a href="#配置Fedora42" class="headerlink" title="配置Fedora42"></a>配置Fedora42</h3><p>1.安装基础工具</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf install -y <span class="built_in">sudo</span> passwd ncurses findutils</span><br></pre></td></tr></table></figure><p>2.创建普通用户</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">useradd -m -G wheel yourusername</span><br><span class="line">passwd yourusername</span><br></pre></td></tr></table></figure><p>3.配置wsl默认使用新建的用户登录</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF &gt; /etc/wsl.conf</span></span><br><span class="line"><span class="string">[boot]</span></span><br><span class="line"><span class="string">systemd=true</span></span><br><span class="line"><span class="string">[user]</span></span><br><span class="line"><span class="string">default=yourusername </span></span><br><span class="line"><span class="string">[network]</span></span><br><span class="line"><span class="string">generateHosts = true</span></span><br><span class="line"><span class="string">generateResolvConf = true</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><p>4.检查dnf状态</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf --version</span><br></pre></td></tr></table></figure><p>5.设置源</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> sed -e <span class="string">&#x27;s|^metalink=|#metalink=|g&#x27;</span> \</span><br><span class="line">         -e <span class="string">&#x27;s|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://mirrors.tuna.tsinghua.edu.cn/fedora|g&#x27;</span> \</span><br><span class="line">         -i.bak /etc/yum.repos.d/fedora*.repo</span><br></pre></td></tr></table></figure><p>6.更新缓存</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf makecache</span><br></pre></td></tr></table></figure><p>7.设置并发下载</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;max_parallel_downloads=10&quot;</span> | <span class="built_in">tee</span> -a /etc/dnf/dnf.conf</span><br></pre></td></tr></table></figure><p>8.关闭防火墙</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl stop firewalld</span><br><span class="line">systemctl <span class="built_in">disable</span> firewalld</span><br></pre></td></tr></table></figure><p>9.安装字体</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf install -y google-noto-fonts-common</span><br></pre></td></tr></table></figure><p>如果把它当centos使用，可以安装开发工具包</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dnf groupinstall <span class="string">&quot;Development Tools&quot;</span> -y &amp;&amp; \</span><br><span class="line">dnf install -y golang java-latest-openjdk-devel nodejs git neofetch htop</span><br></pre></td></tr></table></figure><p>为了避免新创建的用户无法使用sudo命令，可以用<code>root</code>执行<code>visudo</code>。找到下面的配置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%wheel  ALL=(ALL)       ALL</span><br></pre></td></tr></table></figure><p>确认是没备注是的，它可以允许所有属于wheel组的用户执行任何命令。</p><p>然后退出<code>Fedroa42</code>，执行<code>wsl --shutdown</code>，再次打开就可以使用刚才配置的用户登陆了。</p><p>10.安装fastfetch</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dnf install -y fastfetch</span><br><span class="line"><span class="comment"># 如果你想每次登录都显示，那就加进去。</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;fastfetch&quot;</span> &gt;&gt; ~/.bashrc</span><br></pre></td></tr></table></figure><p>过程中可能会遇到一个错误<code>No valid source (baseurl, mirrorlist or metalink) found for repository &quot;fedora-cisco-openh264&quot;</code></p><p>这个需要编辑<code>/etc/yum.repos.d/fedora-cisco-openh264.repo</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vi /etc/yum.repos.d/fedora-cisco-openh264.repo</span><br></pre></td></tr></table></figure><p> 将里面的 <code>enable=1</code>全部修改为<code>enable=0</code>，然后保存退出</p><h3 id="配置Arch"><a href="#配置Arch" class="headerlink" title="配置Arch"></a>配置Arch</h3><p>大致和<code>Fedroa42</code>类似，但也有特殊的地方。注意，此时登陆的用户是<code>root</code></p><p>1.初始化<code>pacman-key</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pacman-key --init</span><br><span class="line">pacman-key --populate archlinux</span><br><span class="line">pacman -Syu</span><br></pre></td></tr></table></figure><p>2.配置源</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/\$repo/os/\$arch&quot;</span> | <span class="built_in">tee</span> /etc/pacman.d/mirrorlist</span><br></pre></td></tr></table></figure><p>然后再次执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pacman-key --init</span><br><span class="line">pacman-key --populate archlinux</span><br></pre></td></tr></table></figure><p>3.安装基础工具包</p><p>archlinux，默认连vi、sudo都没装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S base-devel git vim go</span><br></pre></td></tr></table></figure><p>4.创建用户</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">useradd -m -G wheel yourusername</span><br><span class="line">passwd yourusername</span><br></pre></td></tr></table></figure><p>注意：archlinux的<code>wheel</code>组不能直接执行sudo。</p><p>5.配置wsl</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF &gt; /etc/wsl.conf</span></span><br><span class="line"><span class="string">[boot]</span></span><br><span class="line"><span class="string">systemd=true</span></span><br><span class="line"><span class="string">[user]</span></span><br><span class="line"><span class="string">default=yourusername </span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><p>6.配置sudo</p><p>使用当前用户<code>root</code>，执行<code>visudo</code>，找到<code>%wheel ALL=(ALL:ALL) ALL</code>，将前面的<code>#</code>删掉。</p><p>如果忘记执行这一步就重启使用新用户登录，就会提示<code>yourusername is not in the sudoers file.</code>可以在powershell中执行<code>wsl -u root -d Arch</code>，使用root用户登录，然后用root用户修改即可。</p><p>7.确认id</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">id</span> -u yourusername</span><br></pre></td></tr></table></figure><p>8.设置语言</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/locale.gen</span><br><span class="line"></span><br><span class="line"># 将下面的注释放开</span><br><span class="line">en_US.UTF-8 UTF-8</span><br><span class="line">zh_CN.UTF-8 UTF-8</span><br></pre></td></tr></table></figure><p>编译语言包</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">locale-gen</span><br></pre></td></tr></table></figure><p>设置全局生效</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;LANG=en_US.UTF-8&quot;</span> &gt; /etc/locale.conf  <span class="comment"># 多年经验，还是英文好点</span></span><br></pre></td></tr></table></figure><p>9.安装其他工具包</p><p>ArchLinux是高度定制化的，啥都要自己装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">pacman -S base-devel <span class="built_in">sudo</span> git wget curl bash-completion net-tools iputils</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示系统信息</span></span><br><span class="line">pacman -S fastfetch</span><br></pre></td></tr></table></figure><p>然后退出系统，再重新登录，就是刚才新建的用户了。</p><h2 id="配置PowerShell及配色"><a href="#配置PowerShell及配色" class="headerlink" title="配置PowerShell及配色"></a>配置PowerShell及配色</h2><p>一般Windows会自动识别，然后自动创建<code>PowerShell</code>的配置，但也是有延迟的，我都是配置完了才识别出来。由于我已经配好了<code>Debian</code>和<code>Ubuntu</code>，那就直接复制就行，然后改一下名称、图标、启动命令。</p><p>按<code>ctrl+,</code>打开设置，找到左侧菜单中的配置文件，点击<code>+ 添加新配置文件</code>，然后选择复制，复制一个你配置好的即可。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/powershell-new-config.webp!full" alt="powershell-new-config"></p><p>复制后，点击刚才复制的配置，修改一下。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/powershell-new-config-02.webp!full" alt="powershell-new-config-02"></p><p>系统默认会识别出来的，其中<code>Fedroa</code>系统会有自己的图标，ArchLinux没有，使用的默认WSL的图标。再命令行那里，系统识别出来的是<code>C:\WINDOWS\system32\wsl.exe --distribution-id &#123;897ed9c5-dc17-4a23-a17a-45719d59c52b&#125; --cd ~</code>，但其实我更推荐使用<code>-d 系统名称</code>，这样即使删掉在创建，只要名字一样，这个配置就会一直有用。注意这个名字是 <code>--import </code>后面的这个名字。</p><p>不同系统都会有自己独特的配色，例如Ubuntu有个茄子配色，<code>Fedroa</code>是蓝色的，<code>suse</code>是绿色的，下面分享一下我得配色。在PowerShell中，按下快捷键<code>Ctrl + Shift + ,</code>打开Windows Terminal 的 <code>settings.json</code>，在 <code>&quot;schemes&quot;</code> 数组中添加：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;background&quot;</span>: <span class="string">&quot;#002B36&quot;</span>,</span><br><span class="line">    <span class="string">&quot;black&quot;</span>: <span class="string">&quot;#073642&quot;</span>,</span><br><span class="line">    <span class="string">&quot;blue&quot;</span>: <span class="string">&quot;#268BD2&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightBlack&quot;</span>: <span class="string">&quot;#002B36&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightBlue&quot;</span>: <span class="string">&quot;#839496&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightCyan&quot;</span>: <span class="string">&quot;#93A1A1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightGreen&quot;</span>: <span class="string">&quot;#586E75&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightPurple&quot;</span>: <span class="string">&quot;#6C71C4&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightRed&quot;</span>: <span class="string">&quot;#CB4B16&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightWhite&quot;</span>: <span class="string">&quot;#FDF6E3&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightYellow&quot;</span>: <span class="string">&quot;#657B83&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cursorColor&quot;</span>: <span class="string">&quot;#D33682&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cyan&quot;</span>: <span class="string">&quot;#2AA198&quot;</span>,</span><br><span class="line">    <span class="string">&quot;foreground&quot;</span>: <span class="string">&quot;#839496&quot;</span>,</span><br><span class="line">    <span class="string">&quot;green&quot;</span>: <span class="string">&quot;#859900&quot;</span>,</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Debian-Official&quot;</span>,</span><br><span class="line">    <span class="string">&quot;purple&quot;</span>: <span class="string">&quot;#D33682&quot;</span>,</span><br><span class="line">    <span class="string">&quot;red&quot;</span>: <span class="string">&quot;#DC322F&quot;</span>,</span><br><span class="line">    <span class="string">&quot;selectionBackground&quot;</span>: <span class="string">&quot;#073642&quot;</span>,</span><br><span class="line">    <span class="string">&quot;white&quot;</span>: <span class="string">&quot;#EEE8D5&quot;</span>,</span><br><span class="line">    <span class="string">&quot;yellow&quot;</span>: <span class="string">&quot;#B58900&quot;</span></span><br><span class="line">&#125;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;background&quot;</span>: <span class="string">&quot;#0B1D35&quot;</span>,</span><br><span class="line">    <span class="string">&quot;black&quot;</span>: <span class="string">&quot;#000000&quot;</span>,</span><br><span class="line">    <span class="string">&quot;blue&quot;</span>: <span class="string">&quot;#294172&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightBlack&quot;</span>: <span class="string">&quot;#676767&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightBlue&quot;</span>: <span class="string">&quot;#31609A&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightCyan&quot;</span>: <span class="string">&quot;#51A2DA&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightGreen&quot;</span>: <span class="string">&quot;#49A62A&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightPurple&quot;</span>: <span class="string">&quot;#A181BC&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightRed&quot;</span>: <span class="string">&quot;#F66151&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightWhite&quot;</span>: <span class="string">&quot;#FFFFFF&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightYellow&quot;</span>: <span class="string">&quot;#D6BAAD&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cursorColor&quot;</span>: <span class="string">&quot;#51A2DA&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cyan&quot;</span>: <span class="string">&quot;#226A9C&quot;</span>,</span><br><span class="line">    <span class="string">&quot;foreground&quot;</span>: <span class="string">&quot;#D1D1D1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;green&quot;</span>: <span class="string">&quot;#3E7B28&quot;</span>,</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Fedora-DeepBlue&quot;</span>,</span><br><span class="line">    <span class="string">&quot;purple&quot;</span>: <span class="string">&quot;#75507B&quot;</span>,</span><br><span class="line">    <span class="string">&quot;red&quot;</span>: <span class="string">&quot;#C01C28&quot;</span>,</span><br><span class="line">    <span class="string">&quot;selectionBackground&quot;</span>: <span class="string">&quot;#1B3456&quot;</span>,</span><br><span class="line">    <span class="string">&quot;white&quot;</span>: <span class="string">&quot;#D1D1D1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;yellow&quot;</span>: <span class="string">&quot;#B58900&quot;</span></span><br><span class="line">&#125;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;background&quot;</span>: <span class="string">&quot;#300A24&quot;</span>,</span><br><span class="line">    <span class="string">&quot;black&quot;</span>: <span class="string">&quot;#2E3436&quot;</span>,</span><br><span class="line">    <span class="string">&quot;blue&quot;</span>: <span class="string">&quot;#3465A4&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightBlack&quot;</span>: <span class="string">&quot;#555753&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightBlue&quot;</span>: <span class="string">&quot;#729FCF&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightCyan&quot;</span>: <span class="string">&quot;#34E2E2&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightGreen&quot;</span>: <span class="string">&quot;#8AE234&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightPurple&quot;</span>: <span class="string">&quot;#AD7FA8&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightRed&quot;</span>: <span class="string">&quot;#EF2929&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightWhite&quot;</span>: <span class="string">&quot;#EEEEEC&quot;</span>,</span><br><span class="line">    <span class="string">&quot;brightYellow&quot;</span>: <span class="string">&quot;#FCE94F&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cursorColor&quot;</span>: <span class="string">&quot;#FFFFFF&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cyan&quot;</span>: <span class="string">&quot;#06989A&quot;</span>,</span><br><span class="line">    <span class="string">&quot;foreground&quot;</span>: <span class="string">&quot;#FFFFFF&quot;</span>,</span><br><span class="line">    <span class="string">&quot;green&quot;</span>: <span class="string">&quot;#4E9A06&quot;</span>,</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Ubuntu-Official&quot;</span>,</span><br><span class="line">    <span class="string">&quot;purple&quot;</span>: <span class="string">&quot;#75507B&quot;</span>,</span><br><span class="line">    <span class="string">&quot;red&quot;</span>: <span class="string">&quot;#CC0000&quot;</span>,</span><br><span class="line">    <span class="string">&quot;selectionBackground&quot;</span>: <span class="string">&quot;#B6B6B6&quot;</span>,</span><br><span class="line">    <span class="string">&quot;white&quot;</span>: <span class="string">&quot;#D3D7CF&quot;</span>,</span><br><span class="line">    <span class="string">&quot;yellow&quot;</span>: <span class="string">&quot;#C4A000&quot;</span></span><br><span class="line">&#125;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Arch-Carbon&quot;</span>,</span><br><span class="line">    <span class="string">&quot;background&quot;</span>: <span class="string">&quot;#121212&quot;</span>,</span><br><span class="line">    <span class="string">&quot;black&quot;</span>: <span class="string">&quot;#1C1C1C&quot;</span>,</span><br><span class="line">    <span class="string">&quot;blue&quot;</span>: <span class="string">&quot;#5FAFD7&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cyan&quot;</span>: <span class="string">&quot;#5FAF87&quot;</span>,</span><br><span class="line">    <span class="string">&quot;foreground&quot;</span>: <span class="string">&quot;#D0D0D0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;green&quot;</span>: <span class="string">&quot;#87AF87&quot;</span>,</span><br><span class="line">    <span class="string">&quot;purple&quot;</span>: <span class="string">&quot;#AF87AF&quot;</span>,</span><br><span class="line">    <span class="string">&quot;red&quot;</span>: <span class="string">&quot;#AF5F5F&quot;</span>,</span><br><span class="line">    <span class="string">&quot;white&quot;</span>: <span class="string">&quot;#E4E4E4&quot;</span>,</span><br><span class="line">    <span class="string">&quot;yellow&quot;</span>: <span class="string">&quot;#D7AF5F&quot;</span>,</span><br><span class="line">    <span class="string">&quot;cursorColor&quot;</span>: <span class="string">&quot;#1793D1&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这四个配色，下面就以<code>Fedroa</code>为例看下效果，在PowerShell中，按快捷键<code>Ctrl + ,</code>，打开设置，找到<code>Fedora42</code>的配置文件，在<code>其他设置</code>-&gt;<code>外观</code>中选择刚才添加的配置名称<code>Fedora-DeepBlue</code>，然后再次登录查看效果。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/powershell-new-config-03.webp!full" alt="powershell-new-config-03"></p><p>后续再给每个系统安装个<code>oh-my-zsh</code>美化下就好了。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/powershell-wsl-oh-my-zsh.webp!full" alt="powershell-wsl-oh-my-zsh"></p>]]>
    </content>
    <id>https://dev.net.cn/install-linux-with-wsl/</id>
    <link href="https://dev.net.cn/install-linux-with-wsl/"/>
    <published>2026-03-19T06:00:00.000Z</published>
    <summary>
      <![CDATA[<p>中午无聊，打算装两个Linux玩玩，我选择了<code>Fedora42</code>和<code>Arch</code>，这两个系统在<code>WSL</code>中式支持的，但默认安装在C盘，还得再导出导入一次才能换到其他盘，那何必多此一举，直接导入到其他盘即可。下面介绍下本次安装的过程。</p>]]>
    </summary>
    <title>使用wsl安装Linux系统</title>
    <updated>2026-03-19T06:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="CloudFlare" scheme="https://dev.net.cn/categories/CloudFlare/"/>
    <category term="MySQL" scheme="https://dev.net.cn/tags/mysql/"/>
    <category term="Docker" scheme="https://dev.net.cn/tags/docker/"/>
    <category term="PostgreSQL" scheme="https://dev.net.cn/tags/postgresql/"/>
    <category term="S3" scheme="https://dev.net.cn/tags/s3/"/>
    <content>
      <![CDATA[<p>接上一篇，将能迁移的服务都迁移到PostgreSQL后，再配置上自动备份脚本，基本上就可以高枕无忧了，本来想备份到阿里云OSS的，想了想本站几乎是<code>ALL IN Cloudflare</code>，那就直接备份到<code>CloudFlare R2</code>吧，正好也快一点。下面就记录下备份的方法。</p><span id="more"></span><h2 id="创建CloudFlare-R2桶"><a href="#创建CloudFlare-R2桶" class="headerlink" title="创建CloudFlare R2桶"></a>创建CloudFlare R2桶</h2><p>在面板首页-&gt;<code>存储和数据库</code> -&gt; <code>R2对象存储</code>中，创建一个桶，我起的名字就是<code>backup</code>。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/image-20260318214208232.webp!full" alt="创建桶"></p><p>创建完成后发，返回桶列表，点击右下角的<code>Account Deatails</code>下面的<code>Manage</code>，按钮，创建一个Token。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/image-20260318214457248.webp!full" alt="创建Token"></p><div class="note warning modern"><p>1、权限必须是管理员读和写，选对象读和写执行rclone命令会报权限错误。</p><p>2、因为1，所以最好将客户端IP限制一下，反正VPS都是静态的IP地址，不然要是被泄露了那就完犊子了~</p></div><p>创建好后，给你三个信息，分别是<code>访问密钥 ID（access_key_id）</code>、<code>机密访问密钥（secret_access_key）</code>、<code>为 S3 客户端使用管辖权地特定的终结点(endpoint)</code>。暂时记录下来，下一步要用。</p><h2 id="安装Rclone"><a href="#安装Rclone" class="headerlink" title="安装Rclone"></a>安装Rclone</h2><p>要备份到CloudFlare R2，那就需要Rclone，执行下面的命令安装即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt install rclone</span><br></pre></td></tr></table></figure><p>安装完成后，执行<code>rclone config</code>就可以配置了，不过控制台看着乱糟糟的，我直接选创建配置文件的方式了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">touch</span> ~/.config/rclone/rclone.conf</span><br></pre></td></tr></table></figure><p>然后在里面填写如下信息：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[r2-remote]</span></span><br><span class="line"><span class="attr">type</span> = s3</span><br><span class="line"><span class="attr">provider</span> = Cloudflare</span><br><span class="line"><span class="attr">access_key_id</span> = &lt;你的访问密钥&gt;</span><br><span class="line"><span class="attr">secret_access_key</span> = &lt;你的机密访问密钥&gt;</span><br><span class="line"><span class="comment"># 注意，不要带桶名称</span></span><br><span class="line"><span class="attr">endpoint</span> = https://&lt;Account_ID&gt;.r2.cloudflarestorage.com</span><br><span class="line"><span class="attr">no_check_bucket</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>配置完成后，执行如下命令进行测试：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 执行</span></span><br><span class="line">rclone listremotes</span><br><span class="line"><span class="comment"># 输出 </span></span><br><span class="line">r2-remote:</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行</span></span><br><span class="line">rclone lsd r2-remote: -vv</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="comment"># 你自己的桶，其中需要包含刚才创建的</span></span><br></pre></td></tr></table></figure><h2 id="配置脚本"><a href="#配置脚本" class="headerlink" title="配置脚本"></a>配置脚本</h2><h3 id="PostgreSQL"><a href="#PostgreSQL" class="headerlink" title="PostgreSQL"></a>PostgreSQL</h3><p>在<code>/data/script/</code>中创建<code>pgsql-to-r2.sh</code>文件，内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 配置区 ---</span></span><br><span class="line">CONTAINER_NAME=<span class="string">&quot;postgresql&quot;</span></span><br><span class="line">DB_USER=<span class="string">&quot;postgres&quot;</span></span><br><span class="line">BACKUP_DIR=<span class="string">&quot;/data/backups/pgsql&quot;</span></span><br><span class="line">DATE=$(<span class="built_in">date</span> +%Y%m%d_%H%M%S)</span><br><span class="line">FILE_NAME=<span class="string">&quot;pg_all_backup_<span class="variable">$DATE</span>.sql.gz&quot;</span></span><br><span class="line">R2_BUCKET=<span class="string">&quot;r2-remote:backup&quot;</span> <span class="comment"># rclone 配置名:桶名(backup就是刚才创建的桶)</span></span><br><span class="line">RETENTION_DAYS=7</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 执行备份 ---</span></span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="variable">$BACKUP_DIR</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;开始导出 ...&quot;</span></span><br><span class="line">docker <span class="built_in">exec</span> -t <span class="variable">$CONTAINER_NAME</span> pg_dumpall -c -U <span class="variable">$DB_USER</span> | gzip &gt; <span class="variable">$BACKUP_DIR</span>/<span class="variable">$FILE_NAME</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 上传到 R2 ---</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;正在上传 <span class="variable">$FILE_NAME</span> 到 Cloudflare R2...&quot;</span></span><br><span class="line">rclone copy <span class="variable">$BACKUP_DIR</span>/<span class="variable">$FILE_NAME</span> <span class="variable">$R2_BUCKET</span>/pgsql_daily/</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 清理旧数据 ---</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;清理 <span class="variable">$RETENTION_DAYS</span> 天前的本地备份...&quot;</span></span><br><span class="line">find <span class="variable">$BACKUP_DIR</span> -mtime +<span class="variable">$RETENTION_DAYS</span> -<span class="built_in">exec</span> <span class="built_in">rm</span> &#123;&#125; \;</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 可选：清理 R2 上的旧备份 (需要 R2 支持 lifecycle 或用 rclone delete) ---</span></span><br><span class="line"><span class="comment"># rclone delete $R2_BUCKET --min-age $&#123;RETENTION_DAYS&#125;d --dry-run</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;备份任务完成！&quot;</span></span><br></pre></td></tr></table></figure><p>测试</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bash pgsql-to-r2.sh</span><br></pre></td></tr></table></figure><h3 id="MySQL"><a href="#MySQL" class="headerlink" title="MySQL"></a>MySQL</h3><p>在<code>/data/script/</code>中创建<code>mysql-to-r2.sh</code>文件，内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 1. 配置区 ---</span></span><br><span class="line">CONTAINER_NAME=<span class="string">&quot;db&quot;</span>  <span class="comment"># 你的 MySQL 容器名</span></span><br><span class="line">DB_USER=<span class="string">&quot;root&quot;</span></span><br><span class="line">DB_PASSWORD=<span class="string">&quot;password&quot;</span>  </span><br><span class="line">BACKUP_DIR=<span class="string">&quot;/data/backups/mysql&quot;</span></span><br><span class="line">DATE=$(<span class="built_in">date</span> +%Y%m%d_%H%M)</span><br><span class="line">R2_REMOTE=<span class="string">&quot;r2-remote:backup&quot;</span> </span><br><span class="line">RETENTION_DAYS=7</span><br><span class="line"></span><br><span class="line"><span class="comment"># 需要备份的数据库列表</span></span><br><span class="line">DATABASES=(<span class="string">&quot;db1&quot;</span> <span class="string">&quot;db2&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 2. 准备工作 ---</span></span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="variable">$BACKUP_DIR</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[<span class="variable">$DATE</span>] 开始执行 MySQL 分库备份...&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 3. 循环备份 ---</span></span><br><span class="line"><span class="keyword">for</span> DB <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$&#123;DATABASES[@]&#125;</span>&quot;</span>; <span class="keyword">do</span></span><br><span class="line">    FILE_NAME=<span class="string">&quot;<span class="variable">$&#123;DB&#125;</span>_<span class="variable">$&#123;DATE&#125;</span>.sql.gz&quot;</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;正在备份 MySQL 数据库: <span class="variable">$DB</span>...&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 使用 mysqldump 并直接 gzip 压缩</span></span><br><span class="line">    <span class="comment"># --single-transaction: 保证备份期间不锁表（针对 InnoDB）</span></span><br><span class="line">    <span class="comment"># --routines: 备份存储过程和函数</span></span><br><span class="line">    docker <span class="built_in">exec</span> <span class="variable">$CONTAINER_NAME</span> mysqldump -u<span class="variable">$DB_USER</span> -p<span class="variable">$DB_PASSWORD</span> \</span><br><span class="line">        --single-transaction --routines --databases <span class="variable">$DB</span> | gzip &gt; <span class="variable">$BACKUP_DIR</span>/<span class="variable">$FILE_NAME</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> [ <span class="variable">$&#123;PIPESTATUS[0]&#125;</span> -eq 0 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;上传 <span class="variable">$FILE_NAME</span> 到 R2...&quot;</span></span><br><span class="line">        rclone copy <span class="variable">$BACKUP_DIR</span>/<span class="variable">$FILE_NAME</span> <span class="variable">$R2_REMOTE</span>/mysql_daily/</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;错误: <span class="variable">$DB</span> 备份失败！&quot;</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 4. 清理 ---</span></span><br><span class="line">find <span class="variable">$BACKUP_DIR</span> -mtime +<span class="variable">$RETENTION_DAYS</span> -<span class="built_in">exec</span> <span class="built_in">rm</span> &#123;&#125; \;</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;MySQL 备份任务完成。&quot;</span></span><br></pre></td></tr></table></figure><p>执行测试</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bash mysql-to-r2.sh</span><br></pre></td></tr></table></figure><h2 id="配置定时计划"><a href="#配置定时计划" class="headerlink" title="配置定时计划"></a>配置定时计划</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cronteb -e</span><br></pre></td></tr></table></figure><p>填写如下内容：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 凌晨 02:00 备份 PostgreSQL</span></span><br><span class="line">0 2 * * * /bin/bash /data/script/pgsql-to-r2.sh &gt;&gt; /var/log/pg_backup.log 2&gt;&amp;1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 凌晨 03:00 备份 MySQL</span></span><br><span class="line">0 3 * * * /bin/bash /data/script/mysql-to-r2.sh &gt;&gt; /var/log/mysql_backup.log 2&gt;&amp;1</span><br></pre></td></tr></table></figure><h2 id="配置对象生命周期规则"><a href="#配置对象生命周期规则" class="headerlink" title="配置对象生命周期规则"></a>配置对象生命周期规则</h2><p>数据库文件比较小，乐意放几天放几天。我的习惯，30天肯定至少要访问一次的，所以就30天了。</p><p><img src="https://cdn.dev.net.cn/images/2026/03/image-20260318215913289.webp!full" alt="image-20260318215913289"></p>]]>
    </content>
    <id>https://dev.net.cn/how-to-backup-data-to-cloudflare-r2/</id>
    <link href="https://dev.net.cn/how-to-backup-data-to-cloudflare-r2/"/>
    <published>2026-03-18T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>接上一篇，将能迁移的服务都迁移到PostgreSQL后，再配置上自动备份脚本，基本上就可以高枕无忧了，本来想备份到阿里云OSS的，想了想本站几乎是<code>ALL IN Cloudflare</code>，那就直接备份到<code>CloudFlare R2</code>吧，正好也快一点。下面就记录下备份的方法。</p>]]>
    </summary>
    <title>使用Rclone备份数据到CloudFlare R2</title>
    <updated>2026-03-18T12:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Umami" scheme="https://dev.net.cn/categories/Umami/"/>
    <category term="MySQL" scheme="https://dev.net.cn/tags/mysql/"/>
    <category term="Docker" scheme="https://dev.net.cn/tags/docker/"/>
    <category term="PostgreSQL" scheme="https://dev.net.cn/tags/postgresql/"/>
    <content>
      <![CDATA[<p>今天抽空，将自己维护的服务都升级了一下，看了下<code>umami</code>从<code>v3</code>开始就不再支持<code>MySQL</code>，而我目前是用<code>MySql</code>版本的，本着<code>用新不用旧、能早升级早升级</code>的理念，那就只能升级加迁移数据库了，下面分享下过程。</p><h2 id="导出MySql的数据"><a href="#导出MySql的数据" class="headerlink" title="导出MySql的数据"></a>导出MySql的数据</h2><p>直接在宿主机执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -i db mysqldump --no-create-info --default-character-set=utf8mb4 --quick --skip-add-locks \</span><br><span class="line">-u root -p umamidb &gt; /tmp/mydbdump.sql</span><br></pre></td></tr></table></figure><p>根据自己习惯来。</p><h2 id="定义环境变量"><a href="#定义环境变量" class="headerlink" title="定义环境变量"></a>定义环境变量</h2><p>在<code>docker-compose.yaml</code>同级目录下，创建<code>init-db</code>目录，里面创建一个文件<code>init.sql</code>。内容如下：</p><figure class="highlight postgresql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Umami的数据库初始化脚本</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> umamiuser <span class="keyword">WITH</span> <span class="keyword">PASSWORD</span> <span class="string">&#x27;password&#x27;</span>;</span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> umamiuser <span class="keyword">CREATEDB</span>;</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">DATABASE</span> umamidb <span class="keyword">OWNER</span> umamiuser;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Waline 的数据库初始化脚本</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> waline_user <span class="keyword">WITH</span> <span class="keyword">PASSWORD</span> <span class="string">&#x27;password&#x27;</span>;</span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> waline_user <span class="keyword">CREATEDB</span>;</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">DATABASE</span> waline_db <span class="keyword">OWNER</span> waline_user;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在<code>docker-compose.yaml</code>同级目录下，编辑<code>.env</code>文件（没有就创建）</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">MYSQL_ROOT_PASSWORD</span>=<span class="string">password</span></span><br><span class="line"><span class="attr">MYSQL_USER</span>=<span class="string">username</span></span><br><span class="line"><span class="attr">MYSQL_PASSWORD</span>=<span class="string">password</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># PG</span></span><br><span class="line"><span class="attr">POSTGRES_PASSWORD</span>=<span class="string">password</span></span><br><span class="line"><span class="attr">POSTGRES_USER</span>=<span class="string">postgres</span></span><br><span class="line"><span class="attr">POSTGRES_UMAMI_PWD</span>=<span class="string">password</span></span><br><span class="line"><span class="attr">POSTGRES_WALINE_PWD</span>=<span class="string">password</span></span><br><span class="line"><span class="comment"># SMTP</span></span><br><span class="line"><span class="attr">SMTP_PWD</span>=<span class="string">password</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># umami</span></span><br><span class="line"><span class="attr">UMAMI_APP_SECRET</span>=<span class="string">NiMZovEXm2dYdSfHb47sLcljvjFnPesxxxxxxxxxxxx</span></span><br><span class="line"><span class="attr">UMAMI_DATABASE_URL</span>=<span class="string">postgresql://umamiuser:password@db-pg:5432/umamidb</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># waline</span></span><br><span class="line"><span class="attr">WL_JWT_TOKEN</span>=<span class="string">31HHvaaXFJ984pLc2iCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></span><br><span class="line"><span class="attr">WL_AKISMET_KEY</span>=<span class="string">a93dxxxxxxxxxxxxxx</span></span><br><span class="line"><span class="attr">WL_PG_USER</span>=<span class="string">waline_user</span></span><br><span class="line"><span class="attr">WL_PG_DB</span>=<span class="string">waline_db</span></span><br><span class="line"><span class="attr">WL_TURNSTILE_KEY</span>=<span class="string">0x4AAxxxxxxxxxxxxxxxxxx</span></span><br><span class="line"><span class="attr">WL_TURNSTILE_SECRET</span>=<span class="string">0x4AAAxxxxxxxxxxxxxxxxxxxxxx</span></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>执行<code>docker compose config</code>，确认下配置是否生效</p><h2 id="修改docker-compose-yaml"><a href="#修改docker-compose-yaml" class="headerlink" title="修改docker-compose.yaml"></a>修改docker-compose.yaml</h2><p>在<code>docker-compose.yaml</code>中新增<code>postgresql</code>，并将原来的<code>umami</code>替换为<code>postgresql</code>版本。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">mysql:8.4</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">db</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">env_file:</span> <span class="string">.env</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">dbdata:/var/lib/mysql</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;mysqladmin&quot;</span>, <span class="string">&quot;ping&quot;</span>, <span class="string">&quot;-h&quot;</span>, <span class="string">&quot;localhost&quot;</span>, <span class="string">&quot;-u&quot;</span>, <span class="string">&quot;hc_user&quot;</span>, <span class="string">&quot;--password=yourpassword&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">start_period:</span> <span class="string">10s</span> <span class="comment"># 给 MySQL 10秒的宽限期执行初始化</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line">  <span class="attr">db-pg:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres:17-alpine</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">postgresql</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">POSTGRES_USER:</span> <span class="string">postgres</span></span><br><span class="line">      <span class="attr">POSTGRES_PASSWORD:</span> <span class="string">$POSTGRES_PASSWORD</span></span><br><span class="line">      <span class="attr">POSTGRES_DB:</span> <span class="string">postgres</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">pg-data:/var/lib/postgresql/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./init-db:/docker-entrypoint-initdb.d</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD-SHELL&quot;</span>, <span class="string">&quot;pg_isready -U postgres -d postgres&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">5</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">wordpress:</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">wordpress:6.8.3-php8.4-fpm-alpine</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">env_file:</span> <span class="string">.env</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_HOST=db:3306</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_USER=$MYSQL_USER</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_NAME=wordpress</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wordpress:/var/www/html</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./php-config/custom.ini:/usr/local/etc/php/conf.d/custom.ini</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">vps:</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">wordpress:6.8.3-php8.4-fpm-alpine</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">wp-vps</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">env_file:</span> <span class="string">.env</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_HOST=db:3306</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_USER=wp-vps</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">WORDPRESS_DB_NAME=wp_vps</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wp-vps:/var/www/html</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./php-config/custom.ini:/usr/local/etc/php/conf.d/custom.ini</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">webserver:</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wordpress</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">vps</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">umami</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">waline</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:1.28.0-alpine</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">webserver</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80:80&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;443:443&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wordpress:/var/www/html</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wp-vps:/var/www/wp-vps</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">umami-data:/var/www/umami</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">waline-data:/var/www/waline</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./nginx-conf:/etc/nginx/conf.d</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">certbot-etc:/etc/letsencrypt</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">umami:</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="attr">db-pg:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">service_healthy</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">ghcr.io/umami-software/umami:postgresql-latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">umami</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">expose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;3000&#x27;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">DATABASE_URL:</span> <span class="string">$&#123;UMAMI_DATABASE_URL&#125;</span></span><br><span class="line">      <span class="attr">DATABASE_TYPE:</span> <span class="string">postgresql</span></span><br><span class="line">      <span class="attr">APP_SECRET:</span> <span class="string">$&#123;UMAMI_APP_SECRET&#125;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">waline:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">lizheming/waline:latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">waline</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">expose:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8360&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">waline-data:/app/data</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">TZ:</span> <span class="string">&#x27;Asia/Shanghai&#x27;</span></span><br><span class="line">      <span class="comment"># 数据库设置</span></span><br><span class="line">      <span class="attr">PG_HOST:</span> <span class="string">&#x27;db-pg&#x27;</span></span><br><span class="line">      <span class="attr">PG_PORT:</span> <span class="string">&#x27;5432&#x27;</span></span><br><span class="line">      <span class="attr">PG_DB:</span> <span class="string">&#x27;$&#123;WL_PG_DB&#125;&#x27;</span></span><br><span class="line">      <span class="attr">PG_USER:</span> <span class="string">&#x27;$&#123;WL_PG_USER&#125;&#x27;</span></span><br><span class="line">      <span class="attr">PG_PASSWORD:</span> <span class="string">&#x27;$&#123;POSTGRES_WALINE_PWD&#125;&#x27;</span></span><br><span class="line">      <span class="attr">SITE_NAME:</span> <span class="string">&#x27;码农笔记&#x27;</span></span><br><span class="line">      <span class="attr">SITE_URL:</span> <span class="string">&#x27;https://tech.tvzr.com&#x27;</span></span><br><span class="line">      <span class="attr">SECURE_DOMAINS:</span> <span class="string">&#x27;tech.tvzr.com,waline.tvzr.com&#x27;</span></span><br><span class="line">      <span class="attr">AUTHOR_EMAIL:</span> <span class="string">&#x27;me@tvzr.com&#x27;</span></span><br><span class="line">      <span class="attr">IPQPS:</span> <span class="number">20</span></span><br><span class="line">      <span class="attr">JWT_TOKEN:</span> <span class="string">&#x27;$&#123;WL_JWT_TOKEN&#125;&#x27;</span></span><br><span class="line">      <span class="attr">LEVELS:</span> <span class="string">&#x27;0,10,20,50,100,200&#x27;</span></span><br><span class="line">      <span class="attr">AKISMET_KEY:</span> <span class="string">&#x27;$&#123;WL_AKISMET_KEY&#125;&#x27;</span></span><br><span class="line">      <span class="comment">#TURNSTILE_KEY: &#x27;$&#123;WL_TURNSTILE_KEY&#125;&#x27;</span></span><br><span class="line">      <span class="comment">#TURNSTILE_SECRET: &#x27;$&#123;WL_TURNSTILE_SECRET&#125;&#x27;</span></span><br><span class="line">      <span class="attr">SMTP_HOST:</span> <span class="string">&#x27;mail.tvzr.com&#x27;</span></span><br><span class="line">      <span class="attr">SMTP_PORT:</span> <span class="string">&#x27;465&#x27;</span></span><br><span class="line">      <span class="attr">SMTP_USER:</span> <span class="string">&#x27;no.reply@tvzr.com&#x27;</span></span><br><span class="line">      <span class="attr">SMTP_PASS:</span> <span class="string">&#x27;$&#123;SMTP_PWD&#125;&#x27;</span></span><br><span class="line">      <span class="attr">SMTP_SECURE:</span> <span class="string">&#x27;true&#x27;</span></span><br><span class="line">      <span class="attr">SENDER_NAME:</span> <span class="string">&#x27;码农笔记&#x27;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="attr">db-pg:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">service_healthy</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">app-network</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">certbot:</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">webserver</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">certbot/certbot</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">certbot</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">certbot-etc:/etc/letsencrypt</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wordpress:/var/www/html</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">wp-vps:/var/www/wp-vps</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">umami-data:/var/www/umami</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">waline-data:/var/www/waline</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      certonly --webroot </span></span><br><span class="line"><span class="string">      --non-interactive </span></span><br><span class="line"><span class="string">      --expand</span></span><br><span class="line"><span class="string">      -w /var/www/html -d tvzr.com </span></span><br><span class="line"><span class="string">      -w /var/www/wp-vps -d vps.tvzr.com </span></span><br><span class="line"><span class="string">      --email iat@outlook.com </span></span><br><span class="line"><span class="string">      --agree-tos </span></span><br><span class="line"><span class="string">      --no-eff-email </span></span><br><span class="line"><span class="string">      --force-renewal</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">certbot-etc:</span></span><br><span class="line">  <span class="attr">wordpress:</span></span><br><span class="line">  <span class="attr">dbdata:</span></span><br><span class="line">  <span class="attr">wp-vps:</span></span><br><span class="line">  <span class="attr">umami-data:</span></span><br><span class="line">  <span class="attr">waline-data:</span></span><br><span class="line">  <span class="attr">pg-data:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">app-network:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">bridge</span></span><br></pre></td></tr></table></figure><h2 id="重新启动docker-compose"><a href="#重新启动docker-compose" class="headerlink" title="重新启动docker compose"></a>重新启动docker compose</h2><p>在<code>docker-compose.yml</code>目录，执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 停止</span></span><br><span class="line">docker compose down</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">docker compose up -d</span><br><span class="line"><span class="comment"># 此时会自动拉取最新的镜像</span></span><br></pre></td></tr></table></figure><p>启动时，<code>umami</code>会自动检测到URL变更，并且自动初始化所需要的表。</p><p>可以通过<code>docker logs -f umami</code>查看日志，如果日志中出现如下，即代表初始化运行完成：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">All migrations have been successfully applied.</span><br><span class="line"></span><br><span class="line">✓ Database is up to <span class="built_in">date</span>.</span><br><span class="line"></span><br><span class="line">&gt; umami@3.0.3 update-tracker /app</span><br><span class="line">&gt; node scripts/update-tracker.js</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&gt; umami@3.0.3 start-server /app</span><br><span class="line">&gt; node server.js</span><br><span class="line"></span><br><span class="line">   ▲ Next.js 15.5.9</span><br><span class="line">   - Local:        http://localhost:3000</span><br><span class="line">   - Network:      http://0.0.0.0:3000</span><br><span class="line"></span><br><span class="line"> ✓ Starting...</span><br><span class="line"> ✓ Ready <span class="keyword">in</span> 1235m</span><br></pre></td></tr></table></figure><h2 id="转换mysql导出的表"><a href="#转换mysql导出的表" class="headerlink" title="转换mysql导出的表"></a>转换mysql导出的表</h2><p>mysql导出的表不能直接给postgresql使用，可以参考官网的方案进行处理。<a href="https://docs.umami.is/docs/guides/migrate-mysql-postgresql">将MySQL迁移到PostgreSQL</a></p><h3 id="用双引号替换反勾号，使其兼容PostgreSQL。"><a href="#用双引号替换反勾号，使其兼容PostgreSQL。" class="headerlink" title="用双引号替换反勾号，使其兼容PostgreSQL。"></a>用双引号替换反勾号，使其兼容PostgreSQL。</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sed -i <span class="string">&#x27;s/`/&quot;/g&#x27;</span> mydbdump.sql</span><br></pre></td></tr></table></figure><h3 id="清空postgresql的数据"><a href="#清空postgresql的数据" class="headerlink" title="清空postgresql的数据"></a>清空postgresql的数据</h3><p>如果直接导入，会提示如下错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">INSERT 0 13</span><br><span class="line">INSERT 0 201</span><br><span class="line">INSERT 0 1</span><br><span class="line">INSERT 0 2</span><br><span class="line">ERROR:  duplicate key value violates unique constraint <span class="string">&quot;user_pkey&quot;</span></span><br><span class="line">DETAIL:  Key (user_id)=(41e2b680-648e-4b09-bcd7-3e2b10c06264) already exists.</span><br><span class="line">INSERT 0 1</span><br><span class="line">INSERT 0 38</span><br></pre></td></tr></table></figure><p>看日志是里面的主键重复，因为初始化的时候会创建一条数据，我们需要先清空表数据。执行下面的语句</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -i psql -U umamiuser -d umamidb -c <span class="string">&quot;TRUNCATE TABLE \&quot;user\&quot;, \&quot;session\&quot;, \&quot;website\&quot;, \&quot;team\&quot;, \&quot;team_user\&quot;, \&quot;_prisma_migrations\&quot; CASCADE;&quot;</span></span><br><span class="line">TRUNCATE TABLE</span><br></pre></td></tr></table></figure><h3 id="导入数据到postgresql"><a href="#导入数据到postgresql" class="headerlink" title="导入数据到postgresql"></a>导入数据到postgresql</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -i postgresql psql -U umamiuser -d umamidb &lt; /tmp/mydbdump.sql</span><br></pre></td></tr></table></figure><p>此时就可以正常导入数据了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">INSERT 0 13</span><br><span class="line">INSERT 0 201</span><br><span class="line">INSERT 0 1</span><br><span class="line">INSERT 0 2</span><br><span class="line">INSERT 0 2</span><br><span class="line">INSERT 0 1</span><br><span class="line">INSERT 0 386</span><br></pre></td></tr></table></figure><h2 id="升级完成"><a href="#升级完成" class="headerlink" title="升级完成"></a>升级完成</h2><p>到这里就升级完成了，整个过程还是比较简单和顺利的。对比之前，我就需要多运行一个<code>postgresql</code>容器，不过也没有占用多少资源。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CONTAINER ID   NAME         CPU %     MEM USAGE / LIMIT    MEM %     NET I/O           BLOCK I/O         PIDS </span><br><span class="line">520d2037e3b8   webserver    0.00%     9.168MiB / 3.83GiB   0.23%     12.6MB / 14.3MB   9.34MB / 4.1kB    4 </span><br><span class="line">1857b85d681c   umami        0.00%     229.4MiB / 3.83GiB   5.85%     1.15MB / 2.09MB   41.5MB / 16.4kB   44 </span><br><span class="line">4c2f0afb1926   waline       0.00%     169MiB / 3.83GiB     4.31%     33.2kB / 14.3kB   67.1MB / 4.1kB    18 </span><br><span class="line">99783b642219   postgresql   0.02%     33.93MiB / 3.83GiB   0.87%     202kB / 120kB     7.67MB / 88.4MB   7 </span><br><span class="line">f35027f6d422   db           1.26%     460MiB / 3.83GiB     11.73%    8.06MB / 119MB    941MB / 163MB     47 </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/migrating-umami-to-v3-with-postgresql/</id>
    <link href="https://dev.net.cn/migrating-umami-to-v3-with-postgresql/"/>
    <published>2026-03-18T03:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天抽空，将自己维护的服务都升级了一下，看了下<code>umami</code>从<code>v3</code>开始就不再支持<code>MySQL</code>，而我目前是用<code>MySql</code>版本的，本着<code>用新不用旧、能早升级早升级</cod]]>
    </summary>
    <title>升级umami-mysql到v3-postgresql</title>
    <updated>2026-03-18T03:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Git" scheme="https://dev.net.cn/categories/Git/"/>
    <category term="Git" scheme="https://dev.net.cn/tags/git/"/>
    <category term="GitHub" scheme="https://dev.net.cn/tags/github/"/>
    <content>
      <![CDATA[<p>最近将大部分的开发工具都由WinGet替换为scoop了，一切配置妥当后今天发现给GitHub提交代码时，需要重新验证账号，但通过浏览器认证后一直提示一个错误：<code>fatal: ServicePointManager 不支持具有 socks5 方案的代理</code>，我配置代理一般都是<code>http_proxy</code>，因为它兼容性更好，但此时出现的错误又的确是<code>socks5</code>的问题，想来是多年前配置的了，一直使用也没啥问题，后来我删除了<code>credential</code>中的github账号信息，又出现了这个问题。</p><span id="more"></span><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>出现这个问题也比较容易解决，只需要查看下<code>git config</code>，看下都是哪里配置了<code>socks5</code>协议的代理。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --global --list </span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">http.https://github.com.proxy=socks5://127.0.0.1:1080</span><br><span class="line">http.https://github.com.proxy=socks5://127.0.0.1:1080</span><br></pre></td></tr></table></figure><p>将其覆盖一下即可，我本地监听端口是<code>1080</code>，一般默认的是<code>7890</code>，根据自己实际情况修改。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git config --global http.proxy <span class="string">&#x27;http://127.0.0.1:1080&#x27;</span></span><br><span class="line">git config --global https.proxy <span class="string">&#x27;http://127.0.0.1:1080&#x27;</span></span><br><span class="line">git config --global http.https://github.com.proxy <span class="string">&#x27;http://127.0.0.1:1080&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GitHub账号配置"><a href="#GitHub账号配置" class="headerlink" title="GitHub账号配置"></a>GitHub账号配置</h2><p>我本地是配置了ssh-key的，不过也有大量的<code>https://</code>的github项目，懒得一一修改了。直接在<code>GitHub点击头像</code> -&gt; <code>settings</code> -&gt; <code>Developer settings（最下面）</code> -&gt; <code>Personal access tokens</code> -&gt; <code>Tokens(classic)</code>中，点击右上脚的<code>Generate new token</code>按钮，选择<code>Generate new token(classic)</code>，创建新的Token，输出2FA密码后，权限只需要全选<code>repo</code>即可，有效期就一年吧。将生成的token作为GitHub的密码填入即可。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Username for &#x27;https://github.com&#x27;: xxx    </span><br><span class="line">Password for &#x27;https://xxx@github.com&#x27;: </span><br><span class="line">Enumerating objects: 5, done.</span><br><span class="line">Counting objects: 100% (5/5), done.</span><br><span class="line">Delta compression using up to 20 threads</span><br></pre></td></tr></table></figure><h2 id="Git其他配置优化"><a href="#Git其他配置优化" class="headerlink" title="Git其他配置优化"></a>Git其他配置优化</h2><h3 id="开启安全验证"><a href="#开启安全验证" class="headerlink" title="开启安全验证"></a>开启安全验证</h3><p>因为以前公司的Git仓库都是自己签发的证书，所以我关闭了<code>http.sslVerify</code>，现在都是使用GitHub，索性就全部打开吧。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 开启全局安全验证</span></span><br><span class="line">git config --global http.sslVerify <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>当然，以后可能还会使用局域网内部的git服务，那就到时候根据地址单独配置即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 给内网地址关闭安全验证</span></span><br><span class="line">git config --global http.<span class="string">&quot;https://10.254.32.11/&quot;</span>.sslVerify <span class="literal">false</span></span><br><span class="line">git config --global http.<span class="string">&quot;http://10.40.60.22/&quot;</span>.sslVerify <span class="literal">false</span></span><br></pre></td></tr></table></figure><h3 id="美化log输出"><a href="#美化log输出" class="headerlink" title="美化log输出"></a>美化log输出</h3><p>你现在的 <code>git log</code> 应该是默认的黑白列表。既然用了 <code>pwsh</code> (PowerShell Core) 和图标字体，可以加一个酷炫的多彩 Graph 视图。</p><p><strong>添加一个别名 <code>git lg</code>：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --global alias.lg <span class="string">&quot;log --color --graph --pretty=format:&#x27;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&lt;%an&gt;%Creset&#x27; --abbrev-commit&quot;</span></span><br></pre></td></tr></table></figure><h3 id="性能和格式"><a href="#性能和格式" class="headerlink" title="性能和格式"></a>性能和格式</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 性能起飞：开启 fsmonitor</span></span><br><span class="line">git config --global core.fsmonitor <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 路径支持</span></span><br><span class="line">git config --global gui.encoding utf-8</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 换行符处理（Windows 必备，防止拉取代码后全是修改）</span></span><br><span class="line">git config --global core.autocrlf <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 开启更智能的合并策略</span></span><br><span class="line">git config --global pull.rebase <span class="literal">true</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/servicepointmanager-socks5-proxy-not-supported-error/</id>
    <link href="https://dev.net.cn/servicepointmanager-socks5-proxy-not-supported-error/"/>
    <published>2026-03-17T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>最近将大部分的开发工具都由WinGet替换为scoop了，一切配置妥当后今天发现给GitHub提交代码时，需要重新验证账号，但通过浏览器认证后一直提示一个错误：<code>fatal: ServicePointManager 不支持具有 socks5 方案的代理</code>，我配置代理一般都是<code>http_proxy</code>，因为它兼容性更好，但此时出现的错误又的确是<code>socks5</code>的问题，想来是多年前配置的了，一直使用也没啥问题，后来我删除了<code>credential</code>中的github账号信息，又出现了这个问题。</p>]]>
    </summary>
    <title>ServicePointManager不支持具有socks5方案的代理</title>
    <updated>2026-03-17T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="Windows" scheme="https://dev.net.cn/categories/Windows/"/>
    <category term="Windows" scheme="https://dev.net.cn/tags/windows/"/>
    <category term="Scoop" scheme="https://dev.net.cn/tags/scoop/"/>
    <content>
      <![CDATA[<p>最近也是将大量的软件管理从<code>WinGet</code>切换到<code>scoop</code>，WinGet总是出现一些幽灵链接，虽然可以通过注册表扫描等方式将其修复，个人还是很反感注册表。索性将能用scoop管理的也都用scoop管理，不过在使用过程中，还是遇到了一点点问题，也是比较容易解决。</p><span id="more"></span><h2 id="由于连接方在一段时间后没有正确答复或连接的主机没有反应，连接尝试失败。"><a href="#由于连接方在一段时间后没有正确答复或连接的主机没有反应，连接尝试失败。" class="headerlink" title="由于连接方在一段时间后没有正确答复或连接的主机没有反应，连接尝试失败。"></a>由于连接方在一段时间后没有正确答复或连接的主机没有反应，连接尝试失败。</h2><p>这个问题应该也比较容易遇到，本质上就是万恶的GFW，导致某些资源无法连接（S3、google cloud等），这里需要来一手代理即可。</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop config proxy <span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">1080</span></span><br></pre></td></tr></table></figure><p>如果想取消，则执行下面的命令</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop config <span class="built_in">rm</span> proxy</span><br></pre></td></tr></table></figure><h2 id="Couldn’t-find-manifest-for-‘xxx’"><a href="#Couldn’t-find-manifest-for-‘xxx’" class="headerlink" title="Couldn’t find manifest for ‘xxx’"></a>Couldn’t find manifest for ‘xxx’</h2><p>安装miniconda时会出现这个错误是因为 miniconda3 不在 Scoop 的 Main（核心）软件库中，它被收录在专门存放编程相关工具的 Extras 软件库里。只需要添加一下即可。</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">scoop bucket add extras</span><br><span class="line">// 然后重新安装</span><br><span class="line">scoop install miniconda3</span><br></pre></td></tr></table></figure><h2 id="Token-might-be-misconfigured"><a href="#Token-might-be-misconfigured" class="headerlink" title="Token might be misconfigured"></a>Token might be misconfigured</h2><p>很久以前就安装了scoop，但是那个时候只用来管理字体，所以也是很久没正经用过了。为了更新字体的版本，出现了如下这个错误</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">JetBrainsMono<span class="literal">-NF</span>: <span class="number">2.1</span>.<span class="number">0</span> -&gt; <span class="number">3.4</span>.<span class="number">0</span></span><br><span class="line"></span><br><span class="line">Updating one outdated app:</span><br><span class="line"></span><br><span class="line">Updating <span class="string">&#x27;JetBrainsMono-NF&#x27;</span> (<span class="number">2.1</span>.<span class="number">0</span> -&gt; <span class="number">3.4</span>.<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">Downloading new version</span><br><span class="line"></span><br><span class="line">WARN  Token might be misconfigured.</span><br><span class="line"></span><br><span class="line">OperationStopped: C:\Users\xxx\scoop\apps\scoop\current\lib\download.ps1:<span class="number">84</span></span><br><span class="line"></span><br><span class="line">Line </span><br><span class="line"></span><br><span class="line">  <span class="number">84</span>           <span class="keyword">throw</span> <span class="variable">$e</span></span><br><span class="line"></span><br><span class="line">               ~~~~~~~~</span><br><span class="line"></span><br><span class="line">      Response status code does not indicate success: <span class="number">401</span> (Unauthorized). </span><br></pre></td></tr></table></figure><p>看样子是github token过期了，后来申请了新的token，通过如下方式配置后，发现还是不行：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop config github_token &lt;你的Token内容&gt;</span><br></pre></td></tr></table></figure><p>搜索一番后，发现以前配置的似乎是<code>gh_token</code> ，于是将其删除后问题解决。当时也忘记了为啥要配置这个了。</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scoop config <span class="built_in">rm</span> gh_token </span><br></pre></td></tr></table></figure><p>后面遇到了，再更新~</p>]]>
    </content>
    <id>https://dev.net.cn/how-to-fix-common-scoop-errors-on-windows/</id>
    <link href="https://dev.net.cn/how-to-fix-common-scoop-errors-on-windows/"/>
    <published>2026-03-16T04:54:53.000Z</published>
    <summary>
      <![CDATA[<p>最近也是将大量的软件管理从<code>WinGet</code>切换到<code>scoop</code>，WinGet总是出现一些幽灵链接，虽然可以通过注册表扫描等方式将其修复，个人还是很反感注册表。索性将能用scoop管理的也都用scoop管理，不过在使用过程中，还是遇到了一点点问题，也是比较容易解决。</p>]]>
    </summary>
    <title>Windows Scoop工具常见错误及处理方式</title>
    <updated>2026-03-16T04:54:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="OpenClaw" scheme="https://dev.net.cn/categories/OpenClaw/"/>
    <category term="OpenClaw" scheme="https://dev.net.cn/tags/openclaw/"/>
    <content>
      <![CDATA[<p>通过前文配置后OpenClaw后，如果需要运行一些自动化任务、抓取数据等需求还需要通过浏览器来实现。但是默认debian是没有安装的浏览器的，本篇介绍下如何给Debian安装浏览器，并且配置到OpenClaw中。</p><h2 id="下载Chrome浏览器"><a href="#下载Chrome浏览器" class="headerlink" title="下载Chrome浏览器"></a>下载Chrome浏览器</h2><p>对于Debian&#x2F;Ubuntu发行版</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb</span><br></pre></td></tr></table></figure><p>对于CentOS&#x2F;RHEL发行版</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm</span><br></pre></td></tr></table></figure><p>一般deb和rpm包就覆盖了国内大部分用户的发行版。这个地址是可以直接连接的。</p><h2 id="安装浏览器和字体"><a href="#安装浏览器和字体" class="headerlink" title="安装浏览器和字体"></a>安装浏览器和字体</h2><p>对于Debian&#x2F;Ubuntu发行版</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install -y ./google-chrome-stable_current_amd64.deb</span><br></pre></td></tr></table></figure><p>对于CentOS&#x2F;RHEL发行版</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo yum install -y liberation-fonts</span><br><span class="line">sudo yum install -y xdg-utils</span><br><span class="line">sudo yum localinstall -y ./google-chrome-stable_current_x86_64.rpm</span><br></pre></td></tr></table></figure><p>安装<strong>Google Noto Sans CJK</strong>（思源黑体）的字体文件，为了避免浏览器字体确实无法显示，需要单独安装字体</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// debian/ubuntu</span><br><span class="line">sudo apt install -y fonts-noto-cjk</span><br><span class="line">// CentOS/RHEL</span><br><span class="line">sudo yum install -y google-noto-sans-cjk-fonts</span><br></pre></td></tr></table></figure><p>安装完成后，验证浏览器版本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">google-chrome --version</span><br><span class="line"></span><br><span class="line">Google Chrome 146.0.7680.75 </span><br></pre></td></tr></table></figure><h2 id="配置OpenClaw"><a href="#配置OpenClaw" class="headerlink" title="配置OpenClaw"></a>配置OpenClaw</h2><h3 id="使用独立浏览器"><a href="#使用独立浏览器" class="headerlink" title="使用独立浏览器"></a>使用独立浏览器</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw config set browser.defaultProfile &quot;openclaw&quot;</span><br></pre></td></tr></table></figure><h3 id="使用Headless模式（服务器上没显示器-）"><a href="#使用Headless模式（服务器上没显示器-）" class="headerlink" title="使用Headless模式（服务器上没显示器~）"></a>使用Headless模式（服务器上没显示器~）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw config set browser.headless true</span><br></pre></td></tr></table></figure><h3 id="禁用沙盒模式"><a href="#禁用沙盒模式" class="headerlink" title="禁用沙盒模式"></a>禁用沙盒模式</h3><p>避免因权限安全机制无法启动</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw config set browser.noSandbox true</span><br></pre></td></tr></table></figure><h3 id="设置-Chrome-可执行文件路径"><a href="#设置-Chrome-可执行文件路径" class="headerlink" title="设置 Chrome 可执行文件路径"></a>设置 Chrome 可执行文件路径</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw config set browser.executablePath &quot;$(which google-chrome)&quot;</span><br></pre></td></tr></table></figure><h3 id="重启-openclaw-gateway"><a href="#重启-openclaw-gateway" class="headerlink" title="重启 openclaw gateway"></a>重启 openclaw gateway</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw gateway restart</span><br></pre></td></tr></table></figure><h3 id="为-openclaw-打开浏览器"><a href="#为-openclaw-打开浏览器" class="headerlink" title="为 openclaw 打开浏览器"></a>为 openclaw 打开浏览器</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw browser start</span><br></pre></td></tr></table></figure><p>执行命令后，会有如下内容打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">🦞 browser [openclaw] running: true</span><br></pre></td></tr></table></figure><p>没注意看，也可以通过如下命令检查</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw browser status</span><br></pre></td></tr></table></figure><h2 id="使用QQ-Bot测试一下"><a href="#使用QQ-Bot测试一下" class="headerlink" title="使用QQ Bot测试一下"></a>使用QQ Bot测试一下</h2><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-12.png?x-oss-process=style/large"></p><p>让其截图一下试试</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-13.png?x-oss-process=style/large"></p><p>还能修正一下错别字~</p>]]>
    </content>
    <id>https://dev.net.cn/configure-headless-browser-for-openclaw-debian-server/</id>
    <link href="https://dev.net.cn/configure-headless-browser-for-openclaw-debian-server/"/>
    <published>2026-03-13T08:17:31.000Z</published>
    <summary>
      <![CDATA[<p>通过前文配置后OpenClaw后，如果需要运行一些自动化任务、抓取数据等需求还需要通过浏览器来实现。但是默认debian是没有安装的浏览器的，本篇介绍下如何给Debian安装浏览器，并且配置到OpenClaw中。</p>
<h2 id="下载Chrome浏览器"><a hr]]>
    </summary>
    <title>给Debian服务器上的OpenClaw安装浏览器</title>
    <updated>2026-03-13T08:17:31.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="OpenClaw" scheme="https://dev.net.cn/categories/OpenClaw/"/>
    <category term="AI" scheme="https://dev.net.cn/tags/ai/"/>
    <category term="OpenClaw" scheme="https://dev.net.cn/tags/openclaw/"/>
    <content>
      <![CDATA[<h2 id="环境安装"><a href="#环境安装" class="headerlink" title="环境安装"></a>环境安装</h2><p>以Debian为例，配置好SSH-KEY（本机 -&gt; 服务器免密码登录），提前注册号阿里云百炼模型、Kimi模型、DeepSeek模型、bigmodel(GLM)等模型的账号，该实名实名，该充值充值（Kimi充值才能解速）。</p><span id="more"></span><h3 id="安装nvm"><a href="#安装nvm" class="headerlink" title="安装nvm"></a>安装nvm</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update </span><br><span class="line"><span class="built_in">sudo</span> apt upgrade </span><br><span class="line">// 安装依赖</span><br><span class="line"><span class="built_in">sudo</span> apt install build-essential libssl-dev </span><br><span class="line">// 安装nvm</span><br><span class="line">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh  bash </span><br><span class="line">// 重新打开session即可。</span><br><span class="line">// 安装nodejs 24</span><br><span class="line">nvm install 24</span><br><span class="line">// 切换到nvm</span><br><span class="line">nve use 24</span><br></pre></td></tr></table></figure><h3 id="安装git"><a href="#安装git" class="headerlink" title="安装git"></a>安装git</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install git -y</span><br></pre></td></tr></table></figure><h2 id="安装OpenClaw"><a href="#安装OpenClaw" class="headerlink" title="安装OpenClaw"></a>安装OpenClaw</h2><p>本篇使用npm的方式安装</p><p>如果你的服务器没有配置ssh-key到github，那么你还需要配置一下，将ssh替换为https</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global url.<span class="string">&quot;https://github.com/&quot;</span>.insteadOf ssh://git@github.com/</span><br><span class="line">git config --global url.<span class="string">&quot;https://&quot;</span>.insteadOf git://</span><br></pre></td></tr></table></figure><p>然后再进行安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm install -g openclaw@latest</span><br><span class="line"></span><br><span class="line">openclaw onboard --install-daemon</span><br></pre></td></tr></table></figure><p>对比其他一键安装的方式，个人更偏向这种方式，对于新手小白，建议使用一键安装脚本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 脚本负责节点检测、安装和引导</span><br><span class="line">curl -fsSL https://openclaw.ai/install.sh  bash</span><br><span class="line">//或者 只安装二进制</span><br><span class="line">curl -fsSL https://openclaw.ai/install.sh  bash -s -- --no-onboard</span><br></pre></td></tr></table></figure><h2 id="配置OpenClaw"><a href="#配置OpenClaw" class="headerlink" title="配置OpenClaw"></a>配置OpenClaw</h2><h3 id="安全加固免责申明。"><a href="#安全加固免责申明。" class="headerlink" title="安全加固免责申明。"></a>安全加固免责申明。</h3><p>在上一步中，执行了<code>openclaw onboard --install-daemon</code> 命令后，会打开如下所示的内容：</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image.png?x-oss-process=style/large"></p><p>这里通过方向键，选择Yes，回车。</p><h3 id="Onboarding-mode（引导模式）"><a href="#Onboarding-mode（引导模式）" class="headerlink" title="Onboarding mode（引导模式）"></a>Onboarding mode（引导模式）</h3><p>这里选择QuickStart即可。</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-1.png?x-oss-process=style/full"></p><h3 id="Model-auth-Provider-（模型验证）"><a href="#Model-auth-Provider-（模型验证）" class="headerlink" title="Model&#x2F;auth Provider （模型验证）"></a>Model&#x2F;auth Provider （模型验证）</h3><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-2.png?x-oss-process=style/large"></p><p>内置的暂时不需要，我们选择<code>Custom Provider</code> 。</p><p>此时，会让你输入API Base URL,我们以阿里云百炼模型Qwen为例。</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-3.png?x-oss-process=style/full"></p><p>填入阿里云百炼模型URL，选择API Key验证，填写百炼模型的<code>API Key</code>，然后选择<code>OpenAI-compatible</code>，然后输入模型，目前免费体验的模型有qweb3.5-plus（100Wtoken）,当然还有其他的这里只选择一个即可，后面的再添加就是了。</p><h3 id="Select-channel（选择哪个平台的机器人）"><a href="#Select-channel（选择哪个平台的机器人）" class="headerlink" title="Select channel（选择哪个平台的机器人）"></a>Select channel（选择哪个平台的机器人）</h3><p>因为提前创建了飞书、钉钉的机器人，这里就选择飞书，如果没有任何机器人可以选择Skip for now（后续还可以再次添加和修改）。</p><p>选择使用自带的Feishu，然后填写相关信息</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-4.png?x-oss-process=style/large"></p><h3 id="Web-search"><a href="#Web-search" class="headerlink" title="Web search"></a>Web search</h3><p>这个建议使用Kimi的吧，去<a href="https://platform.moonshot.cn/console/api-keys">Moonshot AI 开放平台 - Kimi K2.5 大模型 API 服务</a> 创建一个Key，填写进去即可。其他的就随便。</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-5.png?x-oss-process=style/large"></p><p>此时就安装配置完成了。</p><h3 id="自定义配置"><a href="#自定义配置" class="headerlink" title="自定义配置"></a>自定义配置</h3><p>后面就可以通古WebUI、修改配置文件、命令等方式进行自定义配置了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openclaw configure --section models</span><br></pre></td></tr></table></figure><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-6.png?x-oss-process=style/large"></p><p>选择Local，然后一直到模型，继续选择<code>Custom Provider</code>，接着填写新模型</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-7.png?x-oss-process=style/full"></p><h2 id="通过隧道在本地访问"><a href="#通过隧道在本地访问" class="headerlink" title="通过隧道在本地访问"></a>通过隧道在本地访问</h2><p>默认OpenClaw只绑定127.0.0.1，也非常不建议大家为了方便去反向代理暴露在公网。通常我们使用SSH 隧道连接到这台服务器上，然后在本地打开。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -i C:\Users\xxx\.ssh\vps-server -L 18790:127.0.0.1:18789 root@服务器IP地址</span><br></pre></td></tr></table></figure><ul><li>-i C:\Users\xxx\.ssh\vps-server，表示我是用本地名为vps-server密钥登陆。没配置SSH-KEY登录服务器的同学可以使用密码。</li><li>-L 绑定端口。</li></ul><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-8-scaled.png?x-oss-process=style/large"></p><p>登陆时需要一个token，或者你使用密码也行（需要自己修改配置）</p><p>Token，在你重启gateway时就可以看到</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-9.png?x-oss-process=style/large"></p><h2 id="接入QQ"><a href="#接入QQ" class="headerlink" title="接入QQ"></a>接入QQ</h2><p>打开<a href="https://q.qq.com/#/">QQ开放平台</a>，官方页面会有个OpenClaw快捷开通的链接，点击，创建机器人即可。</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-10.png?x-oss-process=style/large"></p><p>按照文档接入即可。</p><p><img src="https://cdn.dev.net.cn/dev/images/2026/03/image-11.png?x-oss-process=style/large"></p>]]>
    </content>
    <id>https://dev.net.cn/install-openclaw-debian/</id>
    <link href="https://dev.net.cn/install-openclaw-debian/"/>
    <published>2026-03-06T05:42:22.000Z</published>
    <summary>
      <![CDATA[<h2 id="环境安装"><a href="#环境安装" class="headerlink" title="环境安装"></a>环境安装</h2><p>以Debian为例，配置好SSH-KEY（本机 -&gt; 服务器免密码登录），提前注册号阿里云百炼模型、Kimi模型、DeepSeek模型、bigmodel(GLM)等模型的账号，该实名实名，该充值充值（Kimi充值才能解速）。</p>]]>
    </summary>
    <title>Debian安装OpenClaw</title>
    <updated>2026-03-06T05:42:22.000Z</updated>
  </entry>
</feed>
