<?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-08-22T02:00:00.000Z</updated>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="VSCode" scheme="https://dev.net.cn/categories/VSCode/"/>
    <category term="Python" scheme="https://dev.net.cn/tags/python/"/>
    <category term="Go" scheme="https://dev.net.cn/tags/go/"/>
    <category term="Rust" scheme="https://dev.net.cn/tags/rust/"/>
    <category term="VSCode" scheme="https://dev.net.cn/tags/vscode/"/>
    <content>
      <![CDATA[<p>最近使用<code>VS Code</code>学习开发一些小例子，为了避免写Python的时候看到很多Go、Rust的选项，我打算将这三个语言的开发环境按照<code>Profile</code>全部分开，在此记录一下。</p><h2 id="创建三个Profile"><a href="#创建三个Profile" class="headerlink" title="创建三个Profile"></a>创建三个Profile</h2><p>点击菜单栏<code>文件</code> -&gt; <code>首选项</code> -&gt; <code>配置文件</code> -&gt; <code>创建配置文件</code>，分别创建三个<code>Profile</code>：<code>Python Dev</code>、<code>Go Dev</code>、<code>Rust Dev</code>。除了<code>键盘快捷方式</code>选择默认，其他都选择对应的<code>Profile</code>名称。</p><p><img src="https://cdn.dev.net.cn/images/2026/08/vscode-profile.webp!full" alt="vscode-profile"></p><h2 id="Python-Dev"><a href="#Python-Dev" class="headerlink" title="Python Dev"></a>Python Dev</h2><h3 id="扩展列表"><a href="#扩展列表" class="headerlink" title="扩展列表"></a>扩展列表</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><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">ms-python.vscode-pylance</span><br><span class="line">ms-python.python</span><br><span class="line">ms-python.vscode-python-envs</span><br><span class="line">ms-python.debugpy</span><br><span class="line">charliermarsh.ruff</span><br><span class="line">esbenp.prettier-vscode</span><br><span class="line">vue.volar</span><br><span class="line">dbaeumer.vscode-eslint</span><br><span class="line">fastapilabs.fastapi-vscode</span><br><span class="line">pkief.material-icon-theme</span><br><span class="line">ms-vscode.remote-explorer</span><br><span class="line">ms-vscode-remote.remote-ssh</span><br><span class="line">ms-vscode-remote.remote-ssh-edit</span><br><span class="line">njpwerner.autodocstring</span><br><span class="line">kevinrose.vsc-python-indent</span><br><span class="line">ms-toolsai.vscode-jupyter-slideshow</span><br><span class="line">ms-toolsai.jupyter-keymap</span><br><span class="line">ms-toolsai.vscode-jupyter-cell-tags</span><br><span class="line">ms-toolsai.jupyter-renderers</span><br><span class="line">ms-toolsai.jupyter</span><br><span class="line">eamodio.gitlens</span><br><span class="line">ms-vscode-remote.remote-containers</span><br><span class="line">ms-azuretools.vscode-containers</span><br><span class="line">ms-azuretools.vscode-docker</span><br><span class="line">yzhang.markdown-all-in-one</span><br><span class="line">openai.chatgpt</span><br><span class="line">bazz.uv-manager</span><br><span class="line">tamasfe.even-better-toml</span><br></pre></td></tr></table></figure><h3 id="settings-json"><a href="#settings-json" class="headerlink" title="settings.json"></a>settings.json</h3><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><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><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></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">  <span class="attr">&quot;editor.accessibilitySupport&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 编辑器字体大小</span></span><br><span class="line">  <span class="attr">&quot;editor.fontSize&quot;</span><span class="punctuation">:</span> <span class="number">24</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 字体优先级</span></span><br><span class="line">  <span class="comment">// JetBrainsMono NF 用于终端和代码显示</span></span><br><span class="line">  <span class="attr">&quot;editor.fontFamily&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JetBrainsMono NF, MesloLGM Nerd Font Mono,JetBrains Mono, Consolas, monospace&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 开启字体连字，例如 =&gt; != 等符号优化</span></span><br><span class="line">  <span class="attr">&quot;editor.fontLigatures&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Ctrl + 鼠标滚轮缩放字体</span></span><br><span class="line">  <span class="attr">&quot;editor.mouseWheelZoom&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 超长代码行最大 Token 化长度</span></span><br><span class="line">  <span class="attr">&quot;editor.maxTokenizationLineLength&quot;</span><span class="punctuation">:</span> <span class="number">100000</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 文件图标主题</span></span><br><span class="line">  <span class="attr">&quot;workbench.iconTheme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;material-icon-theme&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Python / Pylance</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Python 分析日志级别</span></span><br><span class="line">  <span class="attr">&quot;python.analysis.logLevel&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Information&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 类型检查级别</span></span><br><span class="line">  <span class="comment">// basic = 平衡性能和检查能力</span></span><br><span class="line">  <span class="attr">&quot;python.analysis.typeCheckingMode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;basic&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 自动 import 补全</span></span><br><span class="line">  <span class="attr">&quot;python.analysis.autoImportCompletions&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</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="attr">&quot;python.analysis.indexing&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 优先使用 uv 管理虚拟环境</span></span><br><span class="line">  <span class="attr">&quot;python-envs.alwaysUseUv&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 工作区搜索虚拟环境</span></span><br><span class="line">  <span class="attr">&quot;python-envs.workspaceSearchPaths&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;.venv&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 终端自动激活虚拟环境</span></span><br><span class="line">  <span class="attr">&quot;python.terminal.activateEnvironment&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Python 编辑配置</span></span><br><span class="line">  <span class="attr">&quot;[python]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 保存自动格式化</span></span><br><span class="line">    <span class="attr">&quot;editor.formatOnSave&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 输入时不自动格式化</span></span><br><span class="line">    <span class="comment">// 避免补全和 Ruff 冲突</span></span><br><span class="line">    <span class="attr">&quot;editor.formatOnType&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// Python 使用 Ruff Formatter</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;charliermarsh.ruff&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// PEP8 推荐长度</span></span><br><span class="line">    <span class="attr">&quot;editor.rulers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="number">88</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 保存时自动执行代码操作</span></span><br><span class="line">    <span class="attr">&quot;editor.codeActionsOnSave&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="comment">// 自动整理 import</span></span><br><span class="line">      <span class="attr">&quot;source.organizeImports.ruff&quot;</span><span class="punctuation">:</span> <span class="string">&quot;explicit&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// 自动修复 Ruff 问题</span></span><br><span class="line">      <span class="attr">&quot;source.fixAll.ruff&quot;</span><span class="punctuation">:</span> <span class="string">&quot;explicit&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用 Ruff 原生语言服务器</span></span><br><span class="line">  <span class="attr">&quot;ruff.nativeServer&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Python 测试</span></span><br><span class="line">  <span class="comment">// 启用 pytest</span></span><br><span class="line">  <span class="attr">&quot;python.testing.pytestEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 禁用 unittest</span></span><br><span class="line">  <span class="attr">&quot;python.testing.unittestEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 默认测试目录</span></span><br><span class="line">  <span class="attr">&quot;python.testing.pytestArgs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;tests&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Jupyter</span></span><br><span class="line">  <span class="comment">// Kernel 重启不询问</span></span><br><span class="line">  <span class="attr">&quot;jupyter.askForKernelRestart&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 每个 Python 文件独立交互窗口</span></span><br><span class="line">  <span class="comment">// 适合 AI 实验、Embedding、数据分析</span></span><br><span class="line">  <span class="attr">&quot;jupyter.interactiveWindow.creationMode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;perFile&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Web 开发格式化</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[javascript]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[javascriptreact]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[typescript]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[typescriptreact]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[vue]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[html]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[css]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></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="attr">&quot;[json]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[jsonc]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;[yaml]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Markdown 使用专业 Markdown 插件</span></span><br><span class="line">  <span class="attr">&quot;[markdown]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span></span><br><span class="line">      <span class="string">&quot;yzhang.markdown-all-in-one&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 自动换行</span></span><br><span class="line">    <span class="attr">&quot;editor.wordWrap&quot;</span><span class="punctuation">:</span> <span class="string">&quot;on&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Markdown 预览支持换行</span></span><br><span class="line">  <span class="attr">&quot;markdown.preview.breaks&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Git / GitLens</span></span><br><span class="line">  <span class="comment">// 显示当前行 Git 信息</span></span><br><span class="line">  <span class="attr">&quot;gitlens.currentLine.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 关闭 CodeLens</span></span><br><span class="line">  <span class="comment">// 避免大型项目性能下降</span></span><br><span class="line">  <span class="attr">&quot;gitlens.codeLens.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// GitHub Copilot</span></span><br><span class="line">  <span class="comment">// 开启下一步代码建议</span></span><br><span class="line">  <span class="attr">&quot;github.copilot.nextEditSuggestions.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Agent 调试日志关闭</span></span><br><span class="line">  <span class="comment">// 需要排查问题时再开启</span></span><br><span class="line">  <span class="attr">&quot;github.copilot.chat.agentDebugLog.fileLogging.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Python Docstring</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Google 风格文档字符串</span></span><br><span class="line">  <span class="attr">&quot;autoDocstring.docstringFormat&quot;</span><span class="punctuation">:</span> <span class="string">&quot;google&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 自动推断类型</span></span><br><span class="line">  <span class="attr">&quot;autoDocstring.guessTypes&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 包含参数名称</span></span><br><span class="line">  <span class="attr">&quot;autoDocstring.includeName&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 新行开始生成</span></span><br><span class="line">  <span class="attr">&quot;autoDocstring.startOnNewLine&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 三引号格式</span></span><br><span class="line">  <span class="attr">&quot;autoDocstring.quoteStyle&quot;</span><span class="punctuation">:</span> <span class="string">&quot;\&quot;\&quot;\&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="comment">// =========================================================</span></span><br><span class="line">  <span class="comment">// Python 缩进</span></span><br><span class="line">  <span class="comment">// =========================================================</span></span><br><span class="line">  <span class="comment">// 悬挂缩进使用 Tab</span></span><br><span class="line">  <span class="attr">&quot;pythonIndent.useTabOnHangingIndent&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 删除纯空格行</span></span><br><span class="line">  <span class="attr">&quot;pythonIndent.trimLinesWithOnlyWhitespace&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</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">// =========================================================</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// EditorConfig 使用 TOML 高亮</span></span><br><span class="line">  <span class="attr">&quot;files.associations&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;.editorconfig&quot;</span><span class="punctuation">:</span> <span class="string">&quot;toml&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Windows Terminal</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Windows 默认终端 PowerShell 7</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.windows&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PowerShell&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Windows Terminal 配置</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.windows&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;PowerShell&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="string">&quot;C:\\Program Files\\PowerShell\\7\\pwsh.exe&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Command Prompt&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;$&#123;env:windir&#125;\\System32\\cmd.exe&quot;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Git Bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="string">&quot;Git Bash&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// macOS Terminal</span></span><br><span class="line">  <span class="comment">// macOS 默认终端</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.osx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// macOS Terminal 配置</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.osx&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="comment">// 默认 zsh</span></span><br><span class="line">      <span class="attr">&quot;zsh&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="comment">// 登录 shell</span></span><br><span class="line">          <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">              <span class="string">&quot;-l&quot;</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// bash 备用</span></span><br><span class="line">      <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">              <span class="string">&quot;-l&quot;</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// fish 备用</span></span><br><span class="line">      <span class="attr">&quot;fish&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;fish&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">              <span class="string">&quot;-l&quot;</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Linux 默认 bash</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.linux&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.linux&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;-l&quot;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;zsh&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;-l&quot;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 文件</span></span><br><span class="line">  <span class="attr">&quot;files.watcherExclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">     <span class="attr">&quot;**/.venv/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/__pycache__/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/.pytest_cache/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/.git/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/.idea&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/node_modules/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/dist/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/build/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></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="Go-Dev"><a href="#Go-Dev" class="headerlink" title="Go Dev"></a>Go Dev</h2><h3 id="扩展列表-1"><a href="#扩展列表-1" 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><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">golang.go</span><br><span class="line">ms-azuretools.vscode-docker</span><br><span class="line">ms-azuretools.vscode-containers</span><br><span class="line">redhat.vscode-yaml</span><br><span class="line">ms-kubernetes-tools.vscode-kubernetes-tools</span><br><span class="line">tim-koehler.helm-intellisense</span><br><span class="line">eamodio.gitlens</span><br><span class="line">ms-vscode.makefile-tools</span><br><span class="line">esbenp.prettier-vscode</span><br><span class="line">dbaeumer.vscode-eslint</span><br><span class="line">tamasfe.even-better-toml</span><br><span class="line">yzhang.markdown-all-in-one</span><br><span class="line">vscode-icons-team.vscode-icons</span><br><span class="line">openai.chatgpt</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><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"># 安装gopls</span></span><br><span class="line">go install golang.org/x/tools/gopls@latest</span><br><span class="line"><span class="comment"># 安装debug 提供强大的调试功能，支持断点、单步执行等</span></span><br><span class="line">go install github.com/go<span class="literal">-delve</span>/delve/cmd/dlv@latest</span><br><span class="line"><span class="comment"># 安装Staticcheck</span></span><br><span class="line">go install honnef.co/go/tools/cmd/staticcheck@latest</span><br><span class="line"><span class="comment"># 安装测试生成 自动生成单元测试代码，提高测试效率</span></span><br><span class="line">go install github.com/cweill/gotests/gotests@latest</span><br><span class="line"><span class="comment"># 安装 Tag</span></span><br><span class="line">go install github.com/fatih/gomodifytags@latest</span><br><span class="line"><span class="comment"># 安装interface</span></span><br><span class="line">go install github.com/josharian/impl@latest</span><br></pre></td></tr></table></figure><h3 id="settings-json-1"><a href="#settings-json-1" class="headerlink" title="settings.json"></a>settings.json</h3><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><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><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</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="attr">&quot;editor.accessibilitySupport&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 编辑器字体大小</span></span><br><span class="line">  <span class="attr">&quot;editor.fontSize&quot;</span><span class="punctuation">:</span> <span class="number">24</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// JetBrains Mono Nerd Font</span></span><br><span class="line">  <span class="attr">&quot;editor.fontFamily&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JetBrainsMono NF, MesloLGM Nerd Font Mono, JetBrains Mono, Consolas, monospace&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 开启字体连字</span></span><br><span class="line">  <span class="attr">&quot;editor.fontLigatures&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Ctrl + 滚轮调整字体</span></span><br><span class="line">  <span class="attr">&quot;editor.mouseWheelZoom&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 超长代码行支持</span></span><br><span class="line">  <span class="attr">&quot;editor.maxTokenizationLineLength&quot;</span><span class="punctuation">:</span> <span class="number">100000</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 文件图标</span></span><br><span class="line">  <span class="attr">&quot;workbench.iconTheme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;material-icon-theme&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Go 开发核心</span></span><br><span class="line">  <span class="comment">// 使用 Go Language Server</span></span><br><span class="line">  <span class="attr">&quot;go.useLanguageServer&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 自动更新 Go 工具</span></span><br><span class="line">  <span class="attr">&quot;go.toolsManagement.autoUpdate&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Go 工具安装路径</span></span><br><span class="line">  <span class="comment">// 默认使用 GOPATH/bin</span></span><br><span class="line">  <span class="attr">&quot;go.toolsManagement.path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 格式化工具</span></span><br><span class="line">  <span class="comment">// 使用 gopls 内置 formatter</span></span><br><span class="line">  <span class="attr">&quot;go.formatTool&quot;</span><span class="punctuation">:</span> <span class="string">&quot;default&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 保存自动格式化</span></span><br><span class="line">  <span class="attr">&quot;[go]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.formatOnSave&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 保存自动整理 import</span></span><br><span class="line">    <span class="attr">&quot;editor.codeActionsOnSave&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;source.organizeImports&quot;</span><span class="punctuation">:</span> <span class="string">&quot;explicit&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// Go 文件默认 formatter</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;golang.go&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// gopls 配置</span></span><br><span class="line">  <span class="attr">&quot;gopls&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 语义高亮</span></span><br><span class="line">    <span class="attr">&quot;ui.semanticTokens&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 自动补全参数</span></span><br><span class="line">    <span class="attr">&quot;ui.completion.usePlaceholders&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// Inlay Hint</span></span><br><span class="line">    <span class="attr">&quot;ui.inlayhint.hints&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="comment">// 结构体字段</span></span><br><span class="line">      <span class="attr">&quot;compositeLiteralFields&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// 常量值</span></span><br><span class="line">      <span class="attr">&quot;constantValues&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// 参数名称提示</span></span><br><span class="line">      <span class="attr">&quot;parameterNames&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// range 类型</span></span><br><span class="line">      <span class="attr">&quot;rangeVariableTypes&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// 泛型提示</span></span><br><span class="line">      <span class="attr">&quot;functionTypeParameters&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 代码分析</span></span><br><span class="line">    <span class="attr">&quot;analyses&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="comment">// 未使用参数</span></span><br><span class="line">      <span class="attr">&quot;unusedparams&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// 未使用变量</span></span><br><span class="line">      <span class="attr">&quot;unusedvariable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// nil 检测</span></span><br><span class="line">      <span class="attr">&quot;nilness&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// shadow变量检测</span></span><br><span class="line">      <span class="attr">&quot;shadow&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="comment">// unused write</span></span><br><span class="line">      <span class="attr">&quot;unusedwrite&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// staticcheck</span></span><br><span class="line">    <span class="attr">&quot;staticcheck&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 项目过滤</span></span><br><span class="line">    <span class="attr">&quot;build.directoryFilters&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;-node_modules&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;-vendor&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;-.git&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Go Test</span></span><br><span class="line">  <span class="comment">// 测试参数</span></span><br><span class="line">  <span class="attr">&quot;go.testFlags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;-v&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 测试超时时间</span></span><br><span class="line">  <span class="attr">&quot;go.testTimeout&quot;</span><span class="punctuation">:</span> <span class="string">&quot;60s&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// CodeLens</span></span><br><span class="line">  <span class="attr">&quot;go.enableCodeLens&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;runtest&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Go Debug</span></span><br><span class="line">  <span class="attr">&quot;go.delveConfig&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 使用 DAP 调试协议</span></span><br><span class="line">    <span class="attr">&quot;debugAdapter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dlv-dap&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 不显示全部变量</span></span><br><span class="line">    <span class="attr">&quot;showGlobalVariables&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 不显示寄存器</span></span><br><span class="line">    <span class="attr">&quot;showRegisters&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Golangci-lint</span></span><br><span class="line">  <span class="comment">// 企业代码检查</span></span><br><span class="line">  <span class="attr">&quot;go.lintTool&quot;</span><span class="punctuation">:</span> <span class="string">&quot;golangci-lint&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;go.lintFlags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;--fast&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Git / GitLens</span></span><br><span class="line">  <span class="comment">// 当前行提交信息</span></span><br><span class="line">  <span class="attr">&quot;gitlens.currentLine.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 关闭 CodeLens</span></span><br><span class="line">  <span class="attr">&quot;gitlens.codeLens.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// GitHub Copilot</span></span><br><span class="line">  <span class="attr">&quot;github.copilot.nextEditSuggestions.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;github.copilot.chat.agentDebugLog.fileLogging.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// JSON/YAML/TOML</span></span><br><span class="line">  <span class="attr">&quot;[json]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;[jsonc]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;[yaml]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;redhat.vscode-yaml&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;[toml]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tamasfe.even-better-toml&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Markdown</span></span><br><span class="line">  <span class="attr">&quot;[markdown]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yzhang.markdown-all-in-one&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;editor.wordWrap&quot;</span><span class="punctuation">:</span> <span class="string">&quot;on&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">&quot;markdown.preview.breaks&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Docker / Kubernetes 文件</span></span><br><span class="line">  <span class="attr">&quot;[dockerfile]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ms-azuretools.vscode-containers&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Terminal Windows</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.windows&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PowerShell&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.windows&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;PowerShell&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;C:\\Program Files\\PowerShell\\7\\pwsh.exe&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Git Bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Git Bash&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Terminal macOS</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.osx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.osx&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;zsh&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Terminal Linux / WSL</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.linux&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.linux&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;zsh&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 文件设置</span></span><br><span class="line">  <span class="attr">&quot;files.exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/.git&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/vendor&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/.idea&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/node_modules/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/dist/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/build/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></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="Rust-Dev"><a href="#Rust-Dev" class="headerlink" title="Rust Dev"></a>Rust Dev</h2><h3 id="扩展列表-2"><a href="#扩展列表-2" 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><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">openai.chatgpt</span><br><span class="line">eamodio.gitlens</span><br><span class="line">ms-azuretools.vscode-docker</span><br><span class="line">ms-azuretools.vscode-containers</span><br><span class="line">rust-lang.rust-analyzer</span><br><span class="line">tamasfe.even-better-toml</span><br><span class="line">yzhang.markdown-all-in-one</span><br><span class="line">esbenp.prettier-vscode</span><br><span class="line">dbaeumer.vscode-eslint</span><br><span class="line">vue.volar</span><br><span class="line">pkief.material-icon-theme</span><br><span class="line">vadimcn.vscode-lldb</span><br><span class="line">fill-labs.dependi</span><br><span class="line">usernamehw.errorlens</span><br></pre></td></tr></table></figure><h3 id="settings-json-2"><a href="#settings-json-2" class="headerlink" title="settings.json"></a>settings.json</h3><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><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</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="attr">&quot;editor.accessibilitySupport&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 编辑器字体</span></span><br><span class="line">  <span class="attr">&quot;editor.fontSize&quot;</span><span class="punctuation">:</span> <span class="number">24</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// JetBrainsMono Nerd Font</span></span><br><span class="line">  <span class="attr">&quot;editor.fontFamily&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JetBrainsMono NF, MesloLGM Nerd Font Mono, JetBrains Mono, Consolas, monospace&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 开启符号连字</span></span><br><span class="line">  <span class="attr">&quot;editor.fontLigatures&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Ctrl + 鼠标滚轮调整字体</span></span><br><span class="line">  <span class="attr">&quot;editor.mouseWheelZoom&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 超长代码支持</span></span><br><span class="line">  <span class="attr">&quot;editor.maxTokenizationLineLength&quot;</span><span class="punctuation">:</span> <span class="number">100000</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 文件图标</span></span><br><span class="line">  <span class="attr">&quot;workbench.iconTheme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;material-icon-theme&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Rust Analyzer</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Rust Analyzer 扩展</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.check.command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;clippy&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 保存时检查</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.checkOnSave&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自动导入</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.imports.granularity.group&quot;</span><span class="punctuation">:</span> <span class="string">&quot;module&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自动合并 use</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.imports.merge.glob&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自动补全</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.completion.autoimport.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 内联提示</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.inlayHints.bindingModeHints.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.inlayHints.closureReturnTypeHints.enable&quot;</span><span class="punctuation">:</span> <span class="string">&quot;always&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.inlayHints.parameterHints.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.inlayHints.typeHints.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Rust 格式化</span></span><br><span class="line">  <span class="attr">&quot;[rust]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 保存自动格式化</span></span><br><span class="line">    <span class="attr">&quot;editor.formatOnSave&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// rustfmt</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rust-lang.rust-analyzer&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 保存执行 clippy 修复</span></span><br><span class="line">    <span class="attr">&quot;editor.codeActionsOnSave&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;source.organizeImports&quot;</span><span class="punctuation">:</span> <span class="string">&quot;explicit&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Cargo</span></span><br><span class="line">  <span class="comment">// Cargo 自动检测</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.cargo.autoreload&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 默认 feature 全开</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.cargo.features&quot;</span><span class="punctuation">:</span> <span class="string">&quot;all&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 构建所有 target</span></span><br><span class="line">  <span class="attr">&quot;rust-analyzer.cargo.buildScripts.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Windows PowerShell 7</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.windows&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PowerShell&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.windows&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;PowerShell&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="string">&quot;C:\\Program Files\\PowerShell\\7\\pwsh.exe&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Git Bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Git Bash&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Command Prompt&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span></span><br><span class="line">        <span class="string">&quot;$&#123;env:windir&#125;\\System32\\cmd.exe&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// macOS</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.osx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.osx&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;zsh&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Linux / WSL</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.defaultProfile.linux&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;terminal.integrated.profiles.linux&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;zsh&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zsh&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;-l&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// Git</span></span><br><span class="line">  <span class="comment">// GitLens 当前行信息</span></span><br><span class="line">  <span class="attr">&quot;gitlens.currentLine.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 关闭 CodeLens</span></span><br><span class="line">  <span class="attr">&quot;gitlens.codeLens.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// GitHub Copilot</span></span><br><span class="line">  <span class="attr">&quot;github.copilot.nextEditSuggestions.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;github.copilot.chat.agentDebugLog.fileLogging.enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</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="attr">&quot;files.autoSave&quot;</span><span class="punctuation">:</span> <span class="string">&quot;afterDelay&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 排除 Rust 编译目录</span></span><br><span class="line">  <span class="attr">&quot;files.watcherExclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/target/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/.git/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/.idea&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/node_modules/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/dist/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/build/**&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;editor.linkedEditing&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></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>]]>
    </content>
    <id>https://dev.net.cn/vscode-profile/</id>
    <link href="https://dev.net.cn/vscode-profile/"/>
    <published>2026-08-22T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>最近使用<code>VS Code</code>学习开发一些小例子，为了避免写Python的时候看到很多Go、Rust的选项，我打算将这三个语言的开发环境按照<code>Profile</code>全部分开，在此记录一下。</p>
<h2 id="创建三个Profile">]]>
    </summary>
    <title>VS Code为不同开发环境配置Profile</title>
    <updated>2026-08-22T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="SpringAI" scheme="https://dev.net.cn/categories/SpringAI/"/>
    <category term="Java" scheme="https://dev.net.cn/tags/java/"/>
    <category term="SpringBoot" scheme="https://dev.net.cn/tags/springboot/"/>
    <category term="AI" scheme="https://dev.net.cn/tags/ai/"/>
    <category term="SpringAI" scheme="https://dev.net.cn/tags/SpringAI/"/>
    <category term="Gradle" scheme="https://dev.net.cn/tags/Gradle/"/>
    <content>
      <![CDATA[<p>今天学习下Spring AI <code>2.0.0</code>:</p><ul><li>第一个Spring AI Hello World</li><li>结构化输出</li><li>Tool Calling</li><li>RAG</li><li>Advisor</li></ul><h2 id="环境要求"><a href="#环境要求" class="headerlink" title="环境要求"></a>环境要求</h2><ul><li>JDK : <code>temurin-lts-jdk (25.0.3-9.0.LTS)</code></li><li>Spring Ai : <code>2.0.0</code></li><li>SpringBoot : <code>4.1.0</code></li><li>Gradle : <code>9.5.1</code></li></ul><h2 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h2><p>创建正常的Spring项目即可，添加的依赖有：</p><ul><li>spring-boot-starter-web</li><li>spring-boot-starter-webflux</li><li>spring-ai-starter-model-openai</li><li>lombok</li><li>spring-boot-devtools</li><li>junit</li><li>spring-ai-bom</li></ul><p>我选择的是Gradle，下面就贴一下<code>build.gradle</code>，创建好应该是这样的：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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></pre></td><td class="code"><pre><span class="line">plugins &#123;  </span><br><span class="line">    id <span class="string">&#x27;java&#x27;</span>  </span><br><span class="line">    id <span class="string">&#x27;org.springframework.boot&#x27;</span> version <span class="string">&#x27;4.1.0&#x27;</span>  </span><br><span class="line">    id <span class="string">&#x27;io.spring.dependency-management&#x27;</span> version <span class="string">&#x27;1.1.7&#x27;</span>  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">group = <span class="string">&#x27;cn.net.dev&#x27;</span>  </span><br><span class="line">version = <span class="string">&#x27;0.0.1-SNAPSHOT&#x27;</span>  </span><br><span class="line">description = <span class="string">&#x27;springai&#x27;</span>  </span><br><span class="line">  </span><br><span class="line">java &#123;  </span><br><span class="line">    toolchain &#123;  </span><br><span class="line">        languageVersion = JavaLanguageVersion.of(<span class="number">25</span>)  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">repositories &#123;  </span><br><span class="line">    mavenCentral()  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">ext &#123;  </span><br><span class="line">    <span class="keyword">set</span>(<span class="string">&#x27;springAiVersion&#x27;</span>, <span class="string">&quot;2.0.0&quot;</span>)  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">dependencies &#123;  </span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-webmvc&#x27;</span>  </span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-webflux&#x27;</span>  </span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.ai:spring-ai-starter-model-openai&#x27;</span>  <span class="comment">//换模型可以换这个依赖</span></span><br><span class="line">    compileOnly <span class="string">&#x27;org.projectlombok:lombok&#x27;</span>  </span><br><span class="line">    developmentOnly <span class="string">&#x27;org.springframework.boot:spring-boot-devtools&#x27;</span>  </span><br><span class="line">    annotationProcessor <span class="string">&#x27;org.projectlombok:lombok&#x27;</span>  </span><br><span class="line">    testImplementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-webmvc-test&#x27;</span>  </span><br><span class="line">    testCompileOnly <span class="string">&#x27;org.projectlombok:lombok&#x27;</span>  </span><br><span class="line">    testRuntimeOnly <span class="string">&#x27;org.junit.platform:junit-platform-launcher&#x27;</span>  </span><br><span class="line">    testAnnotationProcessor <span class="string">&#x27;org.projectlombok:lombok&#x27;</span>  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">dependencyManagement &#123;  </span><br><span class="line">    imports &#123;  </span><br><span class="line">        mavenBom <span class="string">&quot;org.springframework.ai:spring-ai-bom:<span class="subst">$&#123;springAiVersion&#125;</span>&quot;</span>  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">tasks.named(<span class="string">&#x27;test&#x27;</span>) &#123;  </span><br><span class="line">    useJUnitPlatform()  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Hello-Spring-Ai"><a href="#Hello-Spring-Ai" class="headerlink" title="Hello Spring Ai"></a>Hello Spring Ai</h2><p>先来第一个SpringAI程序</p><h3 id="application-yml"><a href="#application-yml" class="headerlink" title="application.yml"></a>application.yml</h3><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></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span>  </span><br><span class="line">  <span class="attr">application:</span>  </span><br><span class="line">    <span class="attr">name:</span> <span class="string">springai</span>  </span><br><span class="line">  <span class="attr">ai:</span>  </span><br><span class="line">    <span class="attr">openai:</span>  </span><br><span class="line">      <span class="attr">api-key:</span> <span class="string">$&#123;DEEPSEEK_API_KEY&#125;</span>  </span><br><span class="line">      <span class="attr">base-url:</span> <span class="string">https://api.deepseek.com/v1</span>  </span><br><span class="line">      <span class="attr">chat:</span>  </span><br><span class="line">        <span class="attr">model:</span> <span class="string">deepseek-v4-flash</span></span><br></pre></td></tr></table></figure><p>即使添加了<code>OpenAI</code>的模块，但其实只要是兼容<code>OpenAI</code>协议的都可以配置上去。</p><h3 id="ChatController"><a href="#ChatController" class="headerlink" title="ChatController"></a>ChatController</h3><p>创建<code>controller</code>包，在里面新建一个<code>ChatController</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.net.dev.springai.controller;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.ChatClient;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestParam;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;  </span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Flux;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@RestController</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatController</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;  </span><br><span class="line">    <span class="comment">// 使用构造器自动注入</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ChatController</span><span class="params">(ChatClient.Builder builder)</span> &#123;  </span><br><span class="line">        <span class="built_in">this</span>.chatClient = builder  </span><br><span class="line">                .defaultSystem(<span class="string">&quot;你是一个个专业的Java技术顾问，回答问题简洁准确&quot;</span>)  </span><br><span class="line">                .build();  </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"> * 普通模式  </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> message  </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span>  </span></span><br><span class="line"><span class="comment"> */</span>  </span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/chat&quot;)</span>    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">chat</span><span class="params">(<span class="meta">@RequestParam</span> String message)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> chatClient</span><br><span class="line">        .prompt()  <span class="comment">// 提示词</span></span><br><span class="line">        .user(message)  <span class="comment">// 用户输入的内容</span></span><br><span class="line">        .call()  <span class="comment">// 调用模型</span></span><br><span class="line">        .content();   <span class="comment">// 返回数据</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"> * 流式  </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> message  </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span>  </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">    <span class="meta">@GetMapping(value = &quot;/stream&quot;, produces = &quot;text/event-stream;charset=UTF-8&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> Flux&lt;String&gt; <span class="title function_">streamChat</span><span class="params">(<span class="meta">@RequestParam</span> String message)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt().user(message).stream().content();  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于<code>IDEA</code>,打开<code>运行/调试配置</code>，点击<code>修改选项(alt+M)</code>，点击环境变量(<code>alt+E</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">DEEPSEEK_API_KEY=你的DeepSeek API KEY</span><br></pre></td></tr></table></figure><p>或者创建.env文件，也可以直接设置系统级环境变量。总之，能读取出来即可。</p><p>启动项目后，在浏览器输入<code>http://127.0.0.1:8080/chat?message=JVM的内存结构是怎样的？</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">JVM 内存主要分为以下几个区域： - **程序计数器**：线程私有，记录当前线程执行的字节码行号。 - **虚拟机栈**：线程私有，存储局部变量表、操作数栈、动态链接、方法出口等。每个方法调用对应一个栈帧。 - **本地方法栈**：线程私有，为 Native 方法服务。 - **堆**：线程共享，存放对象实例和数组。是垃圾回收的主要区域，可细分为新生代（Eden、Survivor）和老年代。 - **方法区（元空间）**：线程共享，存储已被加载的类信息、常量、静态变量等。JDK 8 后元空间取代永久代，使用本地内存。 此外，还有**运行时常量池**（属于方法区的一部分）和**直接内存**（NIO 使用的堆外内存）。</span><br></pre></td></tr></table></figure><p>同样的方式，请求<code>http://127.0.0.1:8080/stream?message=JVM的内存结构是怎样的？</code>。这样，一个<code>Spring AI</code>的<code>Hello World</code>就完成了。</p><h2 id="结构化输出"><a href="#结构化输出" class="headerlink" title="结构化输出"></a>结构化输出</h2><p>Spring AI 2.0支持把模型输出映射到Java对象，地产自动处理JSON解析和类型转换。</p><h3 id="单对象输出"><a href="#单对象输出" class="headerlink" title="单对象输出"></a>单对象输出</h3><p>在<code>ChatController</code>中，继续加入如下：</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><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">record</span> <span class="title class_">Product</span><span class="params">(  </span></span><br><span class="line"><span class="params">        String name,  </span></span><br><span class="line"><span class="params">        List&lt;String&gt; prop,  </span></span><br><span class="line"><span class="params">        List&lt;String&gt; cons,  </span></span><br><span class="line"><span class="params">        <span class="type">int</span> score,  </span></span><br><span class="line"><span class="params">        String recommendation  </span></span><br><span class="line"><span class="params">)</span> &#123;  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@GetMapping(&quot;/analyze&quot;)</span>  </span><br><span class="line"><span class="keyword">public</span> Product <span class="title function_">analyzeProduct</span><span class="params">(<span class="meta">@RequestParam</span> String productName)</span> &#123;  </span><br><span class="line">    <span class="keyword">return</span> chatClient.prompt()  </span><br><span class="line">            .user(<span class="string">&quot;分析产品：&quot;</span> + productName + <span class="string">&quot;,给出优缺点和推荐指数&quot;</span>)  </span><br><span class="line">            .call()  </span><br><span class="line">            .entity(Product.class);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后打开浏览器，输入<code>http://127.0.0.1:8080/analyze?productName=美的热水壶</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><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;美的热水壶&quot;</span><span class="punctuation">,</span><span class="attr">&quot;prop&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="string">&quot;加热速度快&quot;</span><span class="punctuation">,</span><span class="string">&quot;自动断电防干烧&quot;</span><span class="punctuation">,</span><span class="string">&quot;双层防烫设计&quot;</span><span class="punctuation">,</span><span class="string">&quot;大容量适合家庭使用&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span><span class="attr">&quot;cons&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="string">&quot;部分型号壶盖密封圈易老化&quot;</span><span class="punctuation">,</span><span class="string">&quot;烧水噪音相对较大&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span><span class="attr">&quot;score&quot;</span><span class="punctuation">:</span><span class="number">9</span><span class="punctuation">,</span><span class="attr">&quot;recommendation&quot;</span><span class="punctuation">:</span><span class="string">&quot;性价比高，适合注重安全和耐用性的家庭用户&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="列表输出"><a href="#列表输出" class="headerlink" title="列表输出"></a>列表输出</h3><p>在<code>ChatController</code>中，继续加入如下：</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><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">record</span> <span class="title class_">Menu</span><span class="params">(  </span></span><br><span class="line"><span class="params">        String  name,  </span></span><br><span class="line"><span class="params">        String ingredient,  </span></span><br><span class="line"><span class="params">        String calories  </span></span><br><span class="line"><span class="params">)</span>&#123;&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@RequestMapping(&quot;/menu&quot;)</span>  </span><br><span class="line"><span class="keyword">public</span> List&lt;Menu&gt; <span class="title function_">recommendMenu</span><span class="params">(<span class="meta">@RequestParam</span> String scene)</span> &#123;  </span><br><span class="line">    <span class="keyword">return</span> chatClient.prompt()  </span><br><span class="line">            .user(<span class="string">&quot;为以下人群推荐5种菜谱：&quot;</span> + scene)  </span><br><span class="line">            .call()  </span><br><span class="line">            .entity(<span class="keyword">new</span> <span class="title class_">ParameterizedTypeReference</span>&lt;List&lt;Menu&gt;&gt;() &#123;  </span><br><span class="line">            &#125;);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动项目后，在浏览器输入：<code>http://127.0.0.1:8080/menu?scene=减脂</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">[</span><span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;蒜蓉西兰花炒鸡胸&quot;</span><span class="punctuation">,</span><span class="attr">&quot;ingredient&quot;</span><span class="punctuation">:</span><span class="string">&quot;鸡胸肉、西兰花、蒜、橄榄油&quot;</span><span class="punctuation">,</span><span class="attr">&quot;calories&quot;</span><span class="punctuation">:</span><span class="string">&quot;150 kcal&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;柠檬烤三文鱼配芦笋&quot;</span><span class="punctuation">,</span><span class="attr">&quot;ingredient&quot;</span><span class="punctuation">:</span><span class="string">&quot;三文鱼、芦笋、柠檬、黑胡椒&quot;</span><span class="punctuation">,</span><span class="attr">&quot;calories&quot;</span><span class="punctuation">:</span><span class="string">&quot;200 kcal&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;番茄菠菜豆腐汤&quot;</span><span class="punctuation">,</span><span class="attr">&quot;ingredient&quot;</span><span class="punctuation">:</span><span class="string">&quot;豆腐、菠菜、番茄、洋葱&quot;</span><span class="punctuation">,</span><span class="attr">&quot;calories&quot;</span><span class="punctuation">:</span><span class="string">&quot;180 kcal&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;凉拌黄瓜鸡蛋丝&quot;</span><span class="punctuation">,</span><span class="attr">&quot;ingredient&quot;</span><span class="punctuation">:</span><span class="string">&quot;黄瓜、鸡蛋、醋、盐&quot;</span><span class="punctuation">,</span><span class="attr">&quot;calories&quot;</span><span class="punctuation">:</span><span class="string">&quot;120 kcal&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;双椒炒海鲜&quot;</span><span class="punctuation">,</span><span class="attr">&quot;ingredient&quot;</span><span class="punctuation">:</span><span class="string">&quot;虾仁、鱿鱼、青椒、红椒&quot;</span><span class="punctuation">,</span><span class="attr">&quot;calories&quot;</span><span class="punctuation">:</span><span class="string">&quot;160 kcal&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><h2 id="Tool-Calling"><a href="#Tool-Calling" class="headerlink" title="Tool Calling"></a>Tool Calling</h2><p><code>Tool Calling</code>是让大模型具备<code>执行力</code>的关键。模型判断什么情况下会调用工具，Spring AI负责执行并把结果返回给模型.<br> 下面模拟两个场景，定义两个工具的实际功能：<br> <code>service/WeatherService.java</code></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><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> cn.net.dev.springai.service;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Slf4j</span>  </span><br><span class="line"><span class="meta">@Service</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WeatherService</span> &#123;  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getWeather</span><span class="params">(String cityName)</span> &#123;  </span><br><span class="line">        log.info(<span class="string">&quot;大模型调用本工具........&quot;</span>);  </span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;&quot;  </span></span><br><span class="line"><span class="string">                🌍 城市: %s (%s)  </span></span><br><span class="line"><span class="string">                ☁️ 天气数据: %s  </span></span><br><span class="line"><span class="string">                &quot;&quot;&quot;</span>.formatted(<span class="string">&quot;北京&quot;</span>, <span class="string">&quot;中国&quot;</span>, <span class="string">&quot;晴&quot;</span>);  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br><span class="line">service/EmailService.java</span><br><span class="line"><span class="keyword">package</span> cn.net.dev.springai.service;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Slf4j</span>  </span><br><span class="line"><span class="meta">@Service</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EmailService</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">send</span><span class="params">(String destEmail, String subject, String body)</span> &#123;  </span><br><span class="line">        log.info(<span class="string">&quot;大模型调用发送邮件工具...&quot;</span>);  </span><br><span class="line">        log.info(<span class="string">&quot;Subject &quot;</span> + subject + <span class="string">&quot; body &quot;</span> + body);  </span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;邮件已发送给&quot;</span> + destEmail;  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>定义工具</strong><br> <code>tools/MyTools.java</code></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><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> cn.net.dev.springai.tools;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> cn.net.dev.springai.service.EmailService;  </span><br><span class="line"><span class="keyword">import</span> cn.net.dev.springai.service.WeatherService;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.tool.annotation.Tool;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.tool.annotation.ToolParam;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Component</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyTools</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> WeatherService weatherService;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> EmailService emailService;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Tool(description = &quot;查询指定城市的实时天气信息&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getWeather</span><span class="params">(<span class="meta">@ToolParam(description = &quot;城市名称，例如：北京&quot;)</span> String cityName)</span> &#123;  </span><br><span class="line">        System.out.println(cityName);  </span><br><span class="line">        ;  </span><br><span class="line">        <span class="keyword">return</span> weatherService.getWeather(cityName);  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Tool(description = &quot;发送邮件&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">sendMail</span><span class="params">(<span class="meta">@ToolParam(description = &quot;收件人邮箱&quot;)</span> String destEmail,  </span></span><br><span class="line"><span class="params">                           <span class="meta">@ToolParam(description = &quot;邮件标题&quot;)</span> String subject,  </span></span><br><span class="line"><span class="params">                           <span class="meta">@ToolParam(description = &quot;邮件内容&quot;)</span> String body)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> emailService.send(destEmail, subject, body);  </span><br><span class="line">  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注册工具到ChatClient</strong><br> <code>controller/ChatController</code></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><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">private</span> MyTools tools;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ChatController</span><span class="params">(ChatClient.Builder builder,MyTools tools)</span> &#123;  </span><br><span class="line">    <span class="built_in">this</span>.chatClient = builder  </span><br><span class="line">            .defaultSystem(<span class="string">&quot;你是一个个专业的Java技术顾问，回答问题简洁准确&quot;</span>)  </span><br><span class="line">            .build();  </span><br><span class="line">  </span><br><span class="line">    <span class="built_in">this</span>.tools = tools;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后新增一个接口</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(&quot;/testTools&quot;)</span>  </span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">testTools</span><span class="params">(<span class="meta">@RequestParam</span> String question)</span> &#123;  </span><br><span class="line">    <span class="keyword">return</span> chatClient  </span><br><span class="line">            .prompt()  </span><br><span class="line">            .user(question)  </span><br><span class="line">            .tools(tools) <span class="comment">// 注册工具类，由模型判断是否调用  </span></span><br><span class="line">            .call().content();  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>尝试发送：</p><p><code>127.0.0.1:8080/testTools?question=今天北京天气怎么样?</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><br><span class="line"></span><br><span class="line"><span class="comment"># 控制台也会打印日志：</span></span><br><span class="line">大模型调用本工具........</span><br></pre></td></tr></table></figure><p><code>127.0.0.1:8080/testTools?question=给contact@dev.net.cn发送邮件，说明天早晨9点要开产品调研会？</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">邮件已成功发送至 **contact@dev.net.cn**。 **邮件内容摘要：** - **主题：** 产品调研会通知 - **正文：** 提醒明天早晨9点准时参加产品调研会 如有需要调整会议时间或补充其他信息，请随时告知。</span><br><span class="line"></span><br><span class="line">2026-07-19T14:57:47.028+08:00  INFO 18316 --- [springai] [nio-8080-exec-5] c.net.dev.springai.service.EmailService  : 大模型调用发送邮件工具...</span><br><span class="line">2026-07-19T14:57:47.029+08:00  INFO 18316 --- [springai] [nio-8080-exec-5] c.net.dev.springai.service.EmailService  : Subject 产品调研会通知 body 您好，</span><br><span class="line"></span><br><span class="line">通知您明天（即次日）早晨9点将召开产品调研会，请准时参加。</span><br><span class="line"></span><br><span class="line">谢谢！</span><br></pre></td></tr></table></figure><h2 id="RAG"><a href="#RAG" class="headerlink" title="RAG"></a>RAG</h2><p>RAG(<code>检索增强生成</code>)，解决大模型因训练数据时效问题，导致出现幻觉，胡乱回答，同时也是解决内部资料能够被大模型读取的场景。</p><h3 id="安装向量数据库"><a href="#安装向量数据库" class="headerlink" title="安装向量数据库"></a>安装向量数据库</h3><p>就是加了个<code>vector</code>类型的<code>PostgreSQL 17</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">docker run -d \</span><br><span class="line">  --name pgvector-db \</span><br><span class="line">  -e POSTGRES_USER=postgres \</span><br><span class="line">  -e POSTGRES_PASSWORD=postgres \</span><br><span class="line">  -e POSTGRES_DB=testdb \</span><br><span class="line">  -p 5432:5432 \</span><br><span class="line">  pgvector/pgvector:pg17</span><br></pre></td></tr></table></figure><h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><p>打开<code>build.gradle</code>,新增依赖：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><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">implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-jdbc&#x27;</span></span><br><span class="line">implementation <span class="string">&#x27;org.springframework.ai:spring-ai-vector-store-advisor&#x27;</span>  </span><br><span class="line">implementation <span class="string">&#x27;org.springframework.ai:spring-ai-starter-vector-store-pgvector&#x27;</span></span><br><span class="line">implementation <span class="string">&#x27;org.postgresql:postgresql:42.7.11&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="修改application-yml"><a href="#修改application-yml" class="headerlink" title="修改application.yml"></a>修改application.yml</h3><p><strong>修改支持embedding</strong>的模型，目前国内支持<code>embedding</code>的有：<code>qwen3.7-text-embedding</code>、<code>BGE-M3</code>、<code>m3e-base</code>。目前我使用的是<code>qwen</code>。</p><p>然后加入<code>datasource</code>、<code>vectorstore</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></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span>  </span><br><span class="line">  <span class="attr">application:</span>  </span><br><span class="line">    <span class="attr">name:</span> <span class="string">springai</span>  </span><br><span class="line">  <span class="attr">datasource:</span>  </span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:postgresql://127.0.0.1:5432/testdb</span>  </span><br><span class="line">    <span class="attr">username:</span> <span class="string">postgres</span>  </span><br><span class="line">    <span class="attr">password:</span> <span class="string">postgres</span>  </span><br><span class="line">  </span><br><span class="line">  <span class="attr">ai:</span>  </span><br><span class="line">    <span class="attr">openai:</span>  </span><br><span class="line">      <span class="attr">api-key:</span> <span class="string">$&#123;QWEN_API_KEY&#125;</span>  </span><br><span class="line">      <span class="attr">chat:</span>  </span><br><span class="line">        <span class="attr">model:</span> <span class="string">glm-5.2</span>  </span><br><span class="line">        <span class="comment"># 改成你自己的工作空间地址</span></span><br><span class="line">        <span class="attr">base-url:</span> <span class="string">https://ws-xxxxx.cn-beijing.maas.aliyuncs.com/compatible-mode/v1</span>  </span><br><span class="line">      <span class="attr">embedding:</span>  </span><br><span class="line">        <span class="attr">model:</span> <span class="string">qwen3.7-text-embedding</span>  </span><br><span class="line">         <span class="comment"># 改成你自己的工作空间地址</span></span><br><span class="line">        <span class="attr">base-url:</span> <span class="string">https://ws-xxxxxxxx.cn-beijing.maas.aliyuncs.com/compatible-mode/v1</span>  </span><br><span class="line">    <span class="comment">#      api-key: $&#123;DEEPSEEK_API_KEY&#125;  </span></span><br><span class="line"><span class="comment">#      base-url: https://api.deepseek.com/v1  </span></span><br><span class="line"><span class="comment">#      chat:  </span></span><br><span class="line"><span class="comment">#        model: deepseek-v4-flash  </span></span><br><span class="line">    <span class="attr">vectorstore:</span>  </span><br><span class="line">      <span class="attr">pgvector:</span>  </span><br><span class="line">        <span class="attr">initialize-schema:</span> <span class="literal">true</span>  </span><br><span class="line">        <span class="attr">index-type:</span> <span class="string">HNSW</span>  </span><br><span class="line">        <span class="attr">distance-type:</span> <span class="string">cosine_distance</span>  </span><br><span class="line">        <span class="comment"># 注意这个维度，和建表的时候要一致</span></span><br><span class="line">        <span class="attr">dimensions:</span> <span class="number">1024</span>  </span><br><span class="line">        <span class="attr">schema-name:</span> <span class="string">public</span>  </span><br><span class="line">        <span class="attr">table-name:</span> <span class="string">vector_store</span></span><br></pre></td></tr></table></figure><p>创建表(<code>实际上配置中配置了自动创建的</code>)</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">CREATE</span> EXTENSION IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> vector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE TABLE</span> IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> public.vector_store (</span><br><span class="line">    id UUID <span class="keyword">PRIMARY KEY</span> <span class="keyword">DEFAULT</span> gen_random_uuid(),</span><br><span class="line">    content TEXT <span class="keyword">NOT NULL</span>,</span><br><span class="line">    metadata JSONB,</span><br><span class="line">    embedding VECTOR(<span class="number">1024</span>)  <span class="comment">--和配置文件中一致</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建 HNSW索引  还有一个是IVFFlat(估计是生产用的吧)</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> vector_store_embedding_idx </span><br><span class="line"><span class="keyword">ON</span> public.vector_store </span><br><span class="line"><span class="keyword">USING</span> HNSW (embedding vector_cosine_ops);</span><br><span class="line"><span class="comment">-- </span></span><br></pre></td></tr></table></figure><h3 id="RAG入库"><a href="#RAG入库" class="headerlink" title="RAG入库"></a>RAG入库</h3><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><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">controller/DocumentIngestionController</span><br><span class="line"><span class="keyword">package</span> cn.net.dev.springai.controller;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.document.Document;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.transformer.splitter.TokenTextSplitter;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.vectorstore.VectorStore;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.PostMapping;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> java.util.List;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@RestController</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DocumentIngestionController</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> VectorStore vectorStore;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> TokenTextSplitter tokenTextSplitter;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/load&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">loadDocument</span><span class="params">()</span> &#123;  </span><br><span class="line">        <span class="comment">// 模拟读取文档  </span></span><br><span class="line">        <span class="type">var</span> <span class="variable">doc1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Document</span>(<span class="string">&quot;Spring AI 2.0 与 2026年7月31日正式发布，支持Java 25 和虚拟线程&quot;</span>);  </span><br><span class="line">        <span class="type">var</span> <span class="variable">doc2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Document</span>(<span class="string">&quot;詹姆斯高斯林最喜欢的语言是Go语言&quot;</span>);  </span><br><span class="line">  </span><br><span class="line">        <span class="comment">// 文本切分、将长文档切分成适合模型处理的快  </span></span><br><span class="line">        List&lt;Document&gt; splitDocs = tokenTextSplitter.apply(List.of(doc1, doc2));  </span><br><span class="line"><span class="comment">// 向量化存储：将每个文本块转换为向量并存入向量数据库  </span></span><br><span class="line">        vectorStore.add(splitDocs);  </span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;%d 个文档已经存入向量数据库pgvector&quot;</span>.formatted(splitDocs.size());  </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></pre></td><td class="code"><pre><span class="line">POST http://localhost:8080/load</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2 个文档已经存入向量数据库pgvector</span></span><br></pre></td></tr></table></figure><p>查看SQL中是否存在</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">SELECT</span> </span><br><span class="line">    id,</span><br><span class="line">    <span class="keyword">LEFT</span>(content, <span class="number">50</span>) <span class="keyword">as</span> content_preview,</span><br><span class="line">    metadata,</span><br><span class="line">    embedding</span><br><span class="line"><span class="keyword">FROM</span> vector_store </span><br><span class="line">LIMIT <span class="number">3</span>;</span><br></pre></td></tr></table></figure><h3 id="RAG问答"><a href="#RAG问答" class="headerlink" title="RAG问答"></a>RAG问答</h3><p>接下来就试试，利用RAG知识库去回答问题<br> <code>controller/RagController</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.net.dev.springai.controller;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.ChatClient;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.vectorstore.SearchRequest;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.vectorstore.VectorStore;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@RestController</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RagController</span> &#123;  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;  </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> VectorStore vectorStore;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RagController</span><span class="params">(<span class="keyword">final</span> ChatClient.Builder builder)</span> &#123;  </span><br><span class="line">        <span class="built_in">this</span>.chatClient = builder.build();  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/ask&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">askRagQuestion</span><span class="params">(String question)</span> &#123;  </span><br><span class="line">  </span><br><span class="line">        <span class="type">var</span> <span class="variable">qaAdvisor</span> <span class="operator">=</span> QuestionAnswerAdvisor.builder(vectorStore)  </span><br><span class="line">                .searchRequest(SearchRequest.builder().topK(<span class="number">3</span>).similarityThreshold(<span class="number">0.7</span>).build())  </span><br><span class="line">                .build();  </span><br><span class="line">  </span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt().user(question)  </span><br><span class="line">                .advisors(qaAdvisor)  </span><br><span class="line">                .call()  </span><br><span class="line">                .content();  </span><br><span class="line">  </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">http://127.0.0.1:8080/ask?question=詹姆斯高斯林最喜欢的语言是什么</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语言。</span><br></pre></td></tr></table></figure><h2 id="Advisor链"><a href="#Advisor链" class="headerlink" title="Advisor链"></a>Advisor链</h2><p><a href="https://docs.spring.io/spring-ai/reference/api/advisors.html">Advisors API</a>提供了一种灵活且强大的方式，拦截、修改和增强Spring应用中的AI驱动交互。调用带有用户文本的AI模型时，可以添加或补充上下文数据，就像拦截器一样。</p><h3 id="创建一个自己的Advisor"><a href="#创建一个自己的Advisor" class="headerlink" title="创建一个自己的Advisor"></a>创建一个自己的Advisor</h3><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><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></pre></td><td class="code"><pre><span class="line">component/AuditAdvisor.java</span><br><span class="line"><span class="keyword">package</span> cn.net.dev.springai.compoent;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> cn.net.dev.springai.repository.AuditLogRepository;  </span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.ChatClientRequest;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.ChatClientResponse;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.advisor.api.CallAdvisor;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.core.Ordered;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> java.util.Objects;  </span><br><span class="line"><span class="keyword">import</span> java.util.UUID;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Slf4j</span>  </span><br><span class="line"><span class="meta">@Component</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuditAdvisor</span> <span class="keyword">implements</span> <span class="title class_">CallAdvisor</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AuditLogRepository auditLog;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AuditAdvisor</span><span class="params">(AuditLogRepository auditLog)</span> &#123;  </span><br><span class="line">        <span class="built_in">this</span>.auditLog = auditLog;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Override</span>  </span><br><span class="line">    <span class="keyword">public</span> ChatClientResponse <span class="title function_">adviseCall</span><span class="params">(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain)</span> &#123;  </span><br><span class="line">        log.info(<span class="string">&quot;====== AuditAdvisor ======&quot;</span>);  </span><br><span class="line">        <span class="comment">// 前置请求  </span></span><br><span class="line">        <span class="type">String</span> <span class="variable">requestId</span> <span class="operator">=</span> UUID.randomUUID().toString();  </span><br><span class="line">        auditLog.logRequest(requestId, chatClientRequest.prompt().toString());  </span><br><span class="line">        <span class="comment">// 执行链中的下一个Advisor  </span></span><br><span class="line">        <span class="type">ChatClientResponse</span> <span class="variable">chatClientResponse</span> <span class="operator">=</span> callAdvisorChain.nextCall(chatClientRequest);  </span><br><span class="line">  </span><br><span class="line">        <span class="keyword">if</span> (chatClientResponse.chatResponse() != <span class="literal">null</span>) &#123;  </span><br><span class="line">            <span class="comment">// 后置：记录响应  </span></span><br><span class="line">            auditLog.logResponse(requestId, Objects.requireNonNull(chatClientResponse.chatResponse().getResult()).getOutput().getText());  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">return</span> chatClientResponse;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Override</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;AuditAdvisor&quot;</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Override</span>  </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOrder</span><span class="params">()</span> &#123;  </span><br><span class="line">        <span class="comment">// 最先执行  </span></span><br><span class="line">        <span class="keyword">return</span> Ordered.HIGHEST_PRECEDENCE;  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="注册AuditAdvisor"><a href="#注册AuditAdvisor" class="headerlink" title="注册AuditAdvisor"></a>注册AuditAdvisor</h3><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><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></pre></td><td class="code"><pre><span class="line">config/ChatClientConfig.java</span><br><span class="line"><span class="keyword">package</span> cn.net.dev.springai.config;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> cn.net.dev.springai.compoent.AuditAdvisor;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.ChatClient;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.memory.ChatMemory;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.memory.MessageWindowChatMemory;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.vectorstore.SearchRequest;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.vectorstore.VectorStore;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Configuration</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatClientConfig</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Bean</span>  </span><br><span class="line">    <span class="keyword">public</span> ChatClient <span class="title function_">chatClientWithMemory</span><span class="params">(  </span></span><br><span class="line"><span class="params">            ChatClient.Builder builder,  </span></span><br><span class="line"><span class="params">            VectorStore vectorStore,</span></span><br><span class="line"><span class="params">            // 注入自己定义的Advisor  </span></span><br><span class="line"><span class="params">            AuditAdvisor auditAdvisor  </span></span><br><span class="line"><span class="params">    )</span> &#123;  </span><br><span class="line">        <span class="type">ChatMemory</span> <span class="variable">chatMemory</span> <span class="operator">=</span> MessageWindowChatMemory.builder().maxMessages(<span class="number">20</span>).build();  </span><br><span class="line">        <span class="keyword">return</span> builder.defaultAdvisors(  </span><br><span class="line">                <span class="comment">// 别忘了注入进来  </span></span><br><span class="line">                auditAdvisor,  </span><br><span class="line">                MessageChatMemoryAdvisor.builder(chatMemory).build(),  </span><br><span class="line">                QuestionAnswerAdvisor.builder(vectorStore)  </span><br><span class="line">                        .searchRequest(SearchRequest.builder().topK(<span class="number">5</span>).build()  </span><br><span class="line">                        ).build()  </span><br><span class="line">        ).defaultSystem(<span class="string">&quot;我是李大爷，一名专业摸鱼工程师&quot;</span>).build();  </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>创建一个repository<code>repository/AuditLogRepository.java</code> </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.net.dev.springai.repository;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">AuditLogRepository</span> &#123;  </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">logRequest</span><span class="params">(String requestId, String content)</span>;  </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">logResponse</span><span class="params">(String requestId, String content)</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实现类 <code>repository/InMemoryAuditLogRepository.java</code></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><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">package</span> cn.net.dev.springai.repository;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Component</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">InMemoryAuditLogRepository</span> <span class="keyword">implements</span> <span class="title class_">AuditLogRepository</span> &#123;  </span><br><span class="line">    <span class="meta">@Override</span>  </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">logRequest</span><span class="params">(String requestId, String content)</span> &#123;  </span><br><span class="line">        System.out.println(<span class="string">&quot;[REQUEST][&quot;</span> + requestId + <span class="string">&quot;] &quot;</span> + content);  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Override</span>  </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">logResponse</span><span class="params">(String requestId, String content)</span> &#123;  </span><br><span class="line">        System.out.println(<span class="string">&quot;[RESPONSE][&quot;</span> + requestId + <span class="string">&quot;] &quot;</span> + content);  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="接入Controller"><a href="#接入Controller" class="headerlink" title="接入Controller"></a>接入Controller</h3><p>改一下之前的<code>ChatController</code>就行。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.net.dev.springai.controller;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> cn.net.dev.springai.tools.MyTools;  </span><br><span class="line"><span class="keyword">import</span> jakarta.servlet.ServletRequest;  </span><br><span class="line"><span class="keyword">import</span> jakarta.servlet.http.HttpServletRequest;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.client.ChatClient;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.memory.ChatMemory;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.core.ParameterizedTypeReference;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestParam;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;  </span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Flux;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> java.util.List;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@RestController</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatController</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 这里必须用刚才配置的，否则不生效</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient memoryChatClient;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">private</span> MyTools tools;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ChatController</span><span class="params">(ChatClient.Builder builder, MyTools tools, ChatClient memoryChatClient)</span> &#123;  </span><br><span class="line">        <span class="comment">// 相当于重新创建的</span></span><br><span class="line">        <span class="built_in">this</span>.chatClient = builder  </span><br><span class="line">                .defaultSystem(<span class="string">&quot;你是一个个专业的Java技术顾问，回答问题简洁准确&quot;</span>)  </span><br><span class="line">                .build();  </span><br><span class="line">  </span><br><span class="line">        <span class="built_in">this</span>.tools = tools;  </span><br><span class="line">        <span class="comment">// 注入ChatClientConfig定义的</span></span><br><span class="line">        <span class="built_in">this</span>.memoryChatClient = memoryChatClient;  </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">     * 普通模式  </span></span><br><span class="line"><span class="comment">     *  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> message  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>  </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/chat&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">chat</span><span class="params">(<span class="meta">@RequestParam</span> String message, HttpServletRequest request)</span> &#123;  </span><br><span class="line">        <span class="type">String</span> <span class="variable">sessionId</span> <span class="operator">=</span> request.getSession().getId();  </span><br><span class="line">        <span class="comment">// return chatClient.prompt().user(message).call().content();  </span></span><br><span class="line">        <span class="keyword">return</span> memoryChatClient.prompt().user(message)  </span><br><span class="line">                .advisors(a -&gt; a.param(  </span><br><span class="line">                        ChatMemory.CONVERSATION_ID,  </span><br><span class="line">                        sessionId  </span><br><span class="line">                ))  </span><br><span class="line">                .call().content();  </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">     * 流式  </span></span><br><span class="line"><span class="comment">     *  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> message  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>  </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line">    <span class="meta">@GetMapping(value = &quot;/stream&quot;, produces = &quot;text/event-stream;charset=UTF-8&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> Flux&lt;String&gt; <span class="title function_">streamChat</span><span class="params">(<span class="meta">@RequestParam</span> String message)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt().user(message).stream().content();  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Product</span><span class="params">(  </span></span><br><span class="line"><span class="params">            String name,  </span></span><br><span class="line"><span class="params">            List&lt;String&gt; prop,  </span></span><br><span class="line"><span class="params">            List&lt;String&gt; cons,  </span></span><br><span class="line"><span class="params">            <span class="type">int</span> score,  </span></span><br><span class="line"><span class="params">            String recommendation  </span></span><br><span class="line"><span class="params">    )</span> &#123;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/analyze&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">analyzeProduct</span><span class="params">(<span class="meta">@RequestParam</span> String productName)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt()  </span><br><span class="line">                .user(<span class="string">&quot;分析产品：&quot;</span> + productName + <span class="string">&quot;,给出优缺点和推荐指数&quot;</span>)  </span><br><span class="line">                .call()  </span><br><span class="line">                .entity(Product.class);  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Menu</span><span class="params">(  </span></span><br><span class="line"><span class="params">            String name,  </span></span><br><span class="line"><span class="params">            String ingredient,  </span></span><br><span class="line"><span class="params">            String calories  </span></span><br><span class="line"><span class="params">    )</span> &#123;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/menu&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> List&lt;Menu&gt; <span class="title function_">recommendMenu</span><span class="params">(<span class="meta">@RequestParam</span> String scene)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt()  </span><br><span class="line">                .user(<span class="string">&quot;为以下人群推荐5种菜谱：&quot;</span> + scene)  </span><br><span class="line">                .call()  </span><br><span class="line">                .entity(<span class="keyword">new</span> <span class="title class_">ParameterizedTypeReference</span>&lt;List&lt;Menu&gt;&gt;() &#123;  </span><br><span class="line">                &#125;);  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/testTools&quot;)</span>  </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">testTools</span><span class="params">(<span class="meta">@RequestParam</span> String question)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> chatClient  </span><br><span class="line">                .prompt()  </span><br><span class="line">                .user(question)  </span><br><span class="line">                .tools(tools) <span class="comment">// 注册工具类，由模型判断是否调用  </span></span><br><span class="line">                .call().content();  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调配用接口：<code>http://127.0.0.1:8080/chat?message=你是谁？</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">我是李大爷，一名专业摸鱼工程师。不过，根据提供的上下文信息，我无法回答“你是谁”这个问题，因为上下文中并未包含关于我身份的相关内容，仅提供了关于詹姆斯·高斯林和Spring AI 2.0的信息。</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">2026-07-19T19:03:38.908+08:00  INFO 21532 --- [springai] [nio-8080-exec-1] c.n.dev.springai.compoent.AuditAdvisor   : ====== AuditAdvisor ======</span><br><span class="line">[REQUEST][6689000b-85f0-41ab-a34f-9b82cdef2681] Prompt&#123;messages=[SystemMessage&#123;textContent=<span class="string">&#x27;我是李大爷，一名专业摸鱼工程师&#x27;</span>, messageType=SYSTEM, metadata=&#123;messageType=SYSTEM&#125;&#125;, UserMessage&#123;content=<span class="string">&#x27;你是谁？&#x27;</span>, metadata=&#123;messageType=USER&#125;, messageType=USER&#125;], modelOptions=org.springframework.ai.openai.OpenAiChatOptions@3571c333&#125;</span><br><span class="line">[RESPONSE][6689000b-85f0-41ab-a34f-9b82cdef2681] 我是李大爷，一名专业摸鱼工程师。不过，根据提供的上下文信息，我无法回答“你是谁”这个问题，因为上下文中并未包含关于我身份的相关内容，仅提供了关于詹姆斯·高斯林和Spring AI 2.0的信息。</span><br><span class="line">已与地址为 <span class="string">&#x27;&#x27;</span>127.0.0.1:62287<span class="string">&#x27;，传输: &#x27;</span>套接字<span class="string">&#x27;&#x27;</span> 的目标虚拟机断开连接</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-spring-ai-01/</id>
    <link href="https://dev.net.cn/learning-spring-ai-01/"/>
    <published>2026-07-19T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天学习下Spring AI <code>2.0.0</code>:</p>
<ul>
<li>第一个Spring AI Hello World</li>
<li>结构化输出</li>
<li>Tool Calling</li>
<li>RAG</li>
<li>Advis]]>
    </summary>
    <title>SpringAI学习笔记（一）- 初识Spring AI 2</title>
    <updated>2026-07-19T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="ChatGPT" scheme="https://dev.net.cn/categories/ChatGPT/"/>
    <category term="CodeX" scheme="https://dev.net.cn/tags/CodeX/"/>
    <category term="ChatGPT" scheme="https://dev.net.cn/tags/ChatGPT/"/>
    <content>
      <![CDATA[<p>上周更新<code>Codex</code>后发现名字变成了<code>ChatGPT</code>，更新带来了新的特性，也带了了新的问题，而且这个问题似乎只针对我，网上很少看到有类似的问题，最终被迫<code>重装系统</code>解决，浪费半天时间。</p><span id="more"></span><h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>更新Codex后，突然弹出来一个对话框：</p><p><img src="https://cdn.dev.net.cn/images/2026/07/openai-chatgpt-error.webp!full" alt="ChatGPT：Windows安装未完成"></p><p>连续点击好几次都提示这样，只能点击<code>继续受限访问</code>，起初，我还以为是普遍问题，坐等<code>OpenAI</code>修复即可。结果更新了几个版本还是这个问题，并且似乎大家都是正常使用的，那就邪了门了。</p><p>我手动设置<code>~/.codex/config.toml</code>，将<code>sandbox = &quot;unelevated&quot;</code>，修改为<code>sandbox = &quot;elevated&quot;</code>。重启<code>ChatGPT</code>后出现如下界面：</p><p><img src="https://cdn.dev.net.cn/images/2026/07/openai-chatgpt-error-02.webp!full" alt="Windows设置未完成"></p><p> 而且还不允许提交，只要提交就卡死不动，并且在Session中也看不到提交的对话信息。想要使用，只能修改为<code>unelevated</code>。</p><h2 id="解决过程"><a href="#解决过程" class="headerlink" title="解决过程"></a>解决过程</h2><p>既然报错了，那就看看日志，通过日志，发现似乎是电脑上的<code>sandbox</code>坏了。我尝试启动<code>Windows Sandbox</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">Windows 沙盒无法初始化。</span><br><span class="line"></span><br><span class="line">Exception of <span class="built_in">type</span> <span class="string">&#x27;System.Exception&#x27;</span> was thrown.</span><br></pre></td></tr></table></figure><p>目前看，的确是我电脑上的沙盒坏了。自2022年买了拯救者以来，还一直没装过系统，中途只是将自带的Windows家庭版升级为<code>专业版</code>。并且装过不少虚拟化、第三方沙盒、模拟器等，大概率这些软件导致<code>Windows Sandbox</code>无法使用了。</p><h3 id="重装系统修复Sandbox"><a href="#重装系统修复Sandbox" class="headerlink" title="重装系统修复Sandbox"></a>重装系统修复Sandbox</h3><p>既然是<code>沙盒</code>先报的错，那就先解决<code>沙盒</code>的问题，把我一周的token烧完了，也没解决沙盒无法启动的问题，那就只能重装系统了。重装系统(<code>Windows11 专业版</code>)后，<code>控制面板</code> -&gt; <code>程序和功能</code> -&gt; <code>启用或关闭Windows功能</code>，如图设置，开启该开启的：</p><p><img src="https://cdn.dev.net.cn/images/2026/07/openai-chatgpt-error-sandbox-01.webp!full" alt="启用或关闭Windows功能"></p><p>实际上，我第一次启用后，沙盒打开一直是黑屏，后来通过下面的方式解决了这个问题：</p><p><strong>第一次</strong>（实际上就是开启上述的功能，我根据自己情况多开了几个）：</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">Enable-WindowsOptionalFeature</span> <span class="literal">-Online</span> <span class="literal">-FeatureName</span> Microsoft<span class="literal">-Hyper-V-All</span> <span class="literal">-All</span> <span class="literal">-NoRestart</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Enable-WindowsOptionalFeature</span> <span class="literal">-Online</span> <span class="literal">-FeatureName</span> VirtualMachinePlatform <span class="literal">-All</span> <span class="literal">-NoRestart</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Enable-WindowsOptionalFeature</span> <span class="literal">-Online</span> <span class="literal">-FeatureName</span> Containers<span class="literal">-DisposableClientVM</span> <span class="literal">-All</span> <span class="literal">-NoRestart</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Enable-WindowsOptionalFeature</span> <span class="literal">-Online</span> <span class="literal">-FeatureName</span> WSL <span class="literal">-All</span> <span class="literal">-NoRestart</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">shutdown /<span class="built_in">r</span> /t <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong>第二次</strong>，设置权限</p><p>1、在开始菜单搜索<code>Windows Sandbox</code>，右键结果选择<code>打开文件所在位置</code>。</p><p>2、在资源管理器中右键<code>Windows Sandbox</code>快捷方式，选择<code>属性</code>。</p><p>3、切换至<code>安全</code>选项卡，点击<code>编辑</code>按钮。</p><p>4、在用户列表中选中<strong>Everyone</strong>，勾选下方<code>完全控制</code>对应的<code>允许</code>复选框。</p><p>5、点击<code>应用</code>后确认所有窗口关闭。</p><p>以上两次操作后，<code>Windows Sandbox</code>还是打开黑屏。</p><p><strong>第三次</strong>，<code>设置</code>-&gt;<code>隐私和安全性</code>-&gt;<code>Windows 安全中心</code>-&gt;<code>设备安全性</code>-&gt;<code>内核隔离</code>-&gt;<code>关闭内存完整性</code>。重启电脑后，打开<code>Windows Sandbox</code>成功，总之，就是很迷。</p><p><img src="https://cdn.dev.net.cn/images/2026/07/openai-chatgpt-error-sandbox-02.webp!full" alt="久违的Sandbox"></p><p>至此，第一个问题解决，下面尝试安装<code>ChatGPT</code>。</p><h3 id="重装ChatGPT"><a href="#重装ChatGPT" class="headerlink" title="重装ChatGPT"></a>重装ChatGPT</h3><p>直接在微软应用商城下载，不过问题依旧。还是老问题。</p><p><img src="https://cdn.dev.net.cn/images/2026/07/openai-chatgpt-error.webp!full" alt="ChatGPT：Windows安装未完成"></p><p>通过查看日志(<code>~/.codex/.sandbox/sandbox.2026-07-16.log</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></pre></td><td class="code"><pre><span class="line">[2026-07-16T03:52:54.378834900+00:00] ensuring sandbox <span class="built_in">users</span> offline=CodexSandboxOffline online=CodexSandboxOnline</span><br><span class="line">[2026-07-16T03:52:54.595113500+00:00] firewall rule configured name=codex_sandbox_offline_block_loopback_udp protocol=17 RemoteAddresses=127.0.0.0/8,::/127 RemotePorts=* LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T03:52:54.737381600+00:00] firewall rule configured name=codex_sandbox_offline_block_loopback_tcp protocol=6 RemoteAddresses=127.0.0.0/8,::/127 RemotePorts=* LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T03:52:54.901864300+00:00] firewall rule configured name=codex_sandbox_offline_block_loopback_tcp protocol=6 RemoteAddresses=127.0.0.0/8,::/127 RemotePorts=1-65535 LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T03:52:55.063508700+00:00] firewall rule configured name=codex_sandbox_offline_block_outbound protocol=256 RemoteAddresses=0.0.0.0-126.255.255.255,128.0.0.0-255.255.255.255,::,::2-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff RemotePorts=* LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T03:52:55.072063+00:00] WFP setup succeeded <span class="keyword">for</span> CodexSandboxOffline with 12 installed filters</span><br><span class="line">[2026-07-16 11:52:55.840 codex-windows-sandbox-setup.exe] setup binary completed</span><br><span class="line">[2026-07-16T03:52:55.869136+00:00] read-acl-only mode: applying <span class="built_in">read</span> ACLs</span><br><span class="line">[2026-07-16T03:52:55.871854500+00:00] granting <span class="built_in">read</span> ACE to C:\Program Files\WindowsApps\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0\app <span class="keyword">for</span> sandbox <span class="built_in">users</span></span><br><span class="line">[2026-07-16T03:52:55.872247500+00:00] grant <span class="built_in">read</span> ACE failed on C:\Program Files\WindowsApps\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0\app <span class="keyword">for</span> sandbox_group: SetNamedSecurityInfoW failed: 5</span><br><span class="line">[2026-07-16T03:52:55.874152400+00:00] granting <span class="built_in">read</span> ACE to C:\Users\liyang\.codex <span class="keyword">for</span> sandbox <span class="built_in">users</span></span><br><span class="line">[2026-07-16T03:52:57.420323300+00:00] granting <span class="built_in">read</span> ACE to C:\Users\liyang\AppData <span class="keyword">for</span> sandbox <span class="built_in">users</span></span><br><span class="line">[2026-07-16T03:52:57.598711600+00:00] <span class="built_in">read</span> ACL run completed with errors: [<span class="string">&quot;grant read ACE failed on C:\\Program Files\\WindowsApps\\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0\\app for sandbox_group: SetNamedSecurityInfoW failed: 5&quot;</span>]</span><br><span class="line">[2026-07-16T03:52:57.598762+00:00] setup error: <span class="built_in">read</span> ACL run had errors</span><br><span class="line">[2026-07-16 11:52:57.598 codex-windows-sandbox-setup.exe] setup error: <span class="built_in">read</span> ACL run had errors</span><br></pre></td></tr></table></figure><p>这个错误在重装系统前就已经看到过，当时没尝试去修改下权限，这次既然都重装系统了，本着大不了再装一次的原则，就是了下给他权限：</p><p><strong>夺取文件夹所有权</strong>：WindowsApps 目录默认属于 <code>TrustedInstaller</code>。我们首先需要将该文件夹及其所有子文件的主人（所有者）变更为本地管理员组（Administrators）：</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">takeown /f <span class="string">&quot;C:\Program Files\WindowsApps\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0&quot;</span> /<span class="built_in">r</span> /d y</span><br></pre></td></tr></table></figure><p><strong>赋予管理员组和当前用户完全控制权限</strong>：所有权夺回后，我们需要显式地为管理员组和你的当前登录账户赋予“完全控制（Full Control）”权限。（注意换成自己的，虽然WindowsApps通过界面进不去，但是可以通过终端进去）</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"># 赋予本地管理员组完全控制权限</span></span><br><span class="line">icacls <span class="string">&quot;C:\Program Files\WindowsApps\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0&quot;</span> /grant administrators:F /t</span><br><span class="line"></span><br><span class="line"><span class="comment"># 赋予当前登录用户完全控制权限</span></span><br><span class="line">icacls <span class="string">&quot;C:\Program Files\WindowsApps\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0&quot;</span> /grant <span class="string">&quot;%username%&quot;</span>:F /t</span><br></pre></td></tr></table></figure><p>重启<code>ChatGPT</code>，结果依旧报错。</p><h3 id="手动运行codex-windows-sandbox-setup-exe"><a href="#手动运行codex-windows-sandbox-setup-exe" class="headerlink" title="手动运行codex-windows-sandbox-setup.exe"></a>手动运行codex-windows-sandbox-setup.exe</h3><p>查到的第二个解决方案，手动以管理员模式运行<code>C:\Users\liyang\AppData\Local\OpenAI\Codex\bin\3135b80b11133333\codex-windows-sandbox-setup.exe</code>，结果自然是然并卵，啥反应都没有。</p><p>继续重装<code>ChatGPT</code>，还是不行，查看日志<code>~/.codex/.sandbox/sandbox.2026-07-16.log</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></pre></td><td class="code"><pre><span class="line">[2026-07-16T04:24:12.248681400+00:00] read-acl-only mode: applying <span class="built_in">read</span> ACLs</span><br><span class="line">[2026-07-16T04:24:12.251059200+00:00] granting <span class="built_in">read</span> ACE to C:\Program Files\WindowsApps\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0\app <span class="keyword">for</span> sandbox <span class="built_in">users</span></span><br><span class="line">[2026-07-16T04:24:14.268263300+00:00] <span class="built_in">read</span> ACL run completed</span><br><span class="line">[2026-07-16T04:24:35.374982400+00:00] ensuring sandbox <span class="built_in">users</span> offline=CodexSandboxOffline online=CodexSandboxOnline</span><br><span class="line">[2026-07-16T04:24:35.634529+00:00] firewall rule configured name=codex_sandbox_offline_block_loopback_udp protocol=17 RemoteAddresses=127.0.0.0/8,::/127 RemotePorts=* LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T04:24:35.789006300+00:00] firewall rule configured name=codex_sandbox_offline_block_loopback_tcp protocol=6 RemoteAddresses=127.0.0.0/8,::/127 RemotePorts=* LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T04:24:35.954009400+00:00] firewall rule configured name=codex_sandbox_offline_block_loopback_tcp protocol=6 RemoteAddresses=127.0.0.0/8,::/127 RemotePorts=1-65535 LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T04:24:36.111124600+00:00] firewall rule configured name=codex_sandbox_offline_block_outbound protocol=256 RemoteAddresses=0.0.0.0-126.255.255.255,128.0.0.0-255.255.255.255,::,::2-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff RemotePorts=* LocalUserAuthorizedList=O:LSD:(A;;CC;;;S-1-5-21-206745364-2144095549-3152526675-1003)</span><br><span class="line">[2026-07-16T04:24:36.120531900+00:00] WFP setup succeeded <span class="keyword">for</span> CodexSandboxOffline with 12 installed filters</span><br><span class="line">[2026-07-16 12:24:36.929 codex-windows-sandbox-setup.exe] setup binary completed</span><br><span class="line">[2026-07-16T04:24:36.953799300+00:00] read-acl-only mode: applying <span class="built_in">read</span> ACLs</span><br><span class="line">[2026-07-16T04:24:36.970146300+00:00] <span class="built_in">read</span> ACL run completed</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>没有<code>grant read ACE failed on C:\\Program Files\\WindowsApps\\OpenAI.Codex_26.707.9981.0_x64__2p2nqsd0c76g0\\app for sandbox_group: SetNamedSecurityInfoW failed: 5</code>这个错误了，似乎是个好消息。</p><p>经过多次下载(包括第三方包)、安装、重置后，还是不行。此时有些怀疑人生，不死心，又问了下朋友们，他们就像装QQ那样，装完就能用，一点问题都没有。</p><h3 id="取消UMP回环"><a href="#取消UMP回环" class="headerlink" title="取消UMP回环"></a>取消UMP回环</h3><p>重装<code>ChatGPT</code>时设置过<code>UMP回环</code>，这次试了下把它去掉</p><p><img src="https://cdn.dev.net.cn/images/2026/07/openai-chatgpt-error-03.webp!full"></p><p><strong>还是不行！！！！</strong></p><p>关闭防火墙？ <strong>还是不行！！！</strong></p><h3 id="一次手误解决问题"><a href="#一次手误解决问题" class="headerlink" title="一次手误解决问题"></a>一次手误解决问题</h3><p>在我将<code>~/.codex/.sandbox/sandbox.2026-07-16.log</code>拖入<code>VSCode</code>时，不小心把下面的<code>setup_marker.json</code>拖进去了，此时<code>VSCode</code>提示无法打开文件。（<code>黑人问号脸.webp</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">The editor could not be opened due to an unexpected error. Please consult the <span class="built_in">log</span> <span class="keyword">for</span> more details.</span><br></pre></td></tr></table></figure><p>继续问<code>AI（此时换了美国豆包）</code>，说：<code>这个错误通常发生在 **VS Code**（或基于它的编辑器）中，当它尝试打开某个文件、特定的自定义编辑器（比如 Markdown 预览、Git 历史或某些插件特有的视图）时，由于后台服务崩溃、文件锁死或配置损坏而导致。</code></p><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"># 1. 强制将 .codex 文件夹及其子文件的“所有者”修改为当前用户</span></span><br><span class="line">takeown /f <span class="string">&quot;C:\Users\liyang\.codex&quot;</span> /<span class="built_in">r</span> /d y</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 重置该文件夹的权限继承，恢复你对该文件夹的完全控制</span></span><br><span class="line">icacls <span class="string">&quot;C:\Users\liyang\.codex&quot;</span> /reset /t</span><br></pre></td></tr></table></figure><p>再次重装，这次真就是装QQ那样一般简单、丝滑了。</p><h2 id="事后复盘"><a href="#事后复盘" class="headerlink" title="事后复盘"></a>事后复盘</h2><p>看起来，似乎还是权限问题，可能是因为处理<code>Sandbox</code>权限的时候导致了这个问题，例如：<code>第二次，设置权限</code>。不过我不想去复现了，能用就行。<code>ChatGPT</code>还有5次重置机会，找个项目先猛猛蹬一会儿。</p><p>此时的状态：</p><p><code>Windows Sandbox</code>      可用</p><p><code>Windows Defender</code>     开启</p><p><code>Clash UMP 回环（对于ChatGPT）</code>         关闭</p><p>使用微软商城安装的<code>ChatGPT</code>（尽可能不要下载第三方的）。</p>]]>
    </content>
    <id>https://dev.net.cn/after-codex-upgraded-to-chatgpt-unable-to-switch-to-elevated-mode/</id>
    <link href="https://dev.net.cn/after-codex-upgraded-to-chatgpt-unable-to-switch-to-elevated-mode/"/>
    <published>2026-07-16T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>上周更新<code>Codex</code>后发现名字变成了<code>ChatGPT</code>，更新带来了新的特性，也带了了新的问题，而且这个问题似乎只针对我，网上很少看到有类似的问题，最终被迫<code>重装系统</code>解决，浪费半天时间。</p>]]>
    </summary>
    <title>Codex升级为ChatGPT后，无法切换为elevated的问题</title>
    <updated>2026-07-16T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Charles</name>
    </author>
    <category term="LangChain" scheme="https://dev.net.cn/categories/LangChain/"/>
    <category term="Python" scheme="https://dev.net.cn/tags/python/"/>
    <category term="LangChain" scheme="https://dev.net.cn/tags/LangChain/"/>
    <category term="LangGraph" scheme="https://dev.net.cn/tags/LangGraph/"/>
    <category term="AI-Agent" scheme="https://dev.net.cn/tags/AI-Agent/"/>
    <content>
      <![CDATA[<h2 id="LangChain介绍"><a href="#LangChain介绍" class="headerlink" title="LangChain介绍"></a>LangChain介绍</h2><p>LangChain 是一个用来开发大模型应用的开源框架。简单说，它可以帮助我们把大语言模型，比如 DeepSeek、OpenAI、Claude 等，和自己的程序、数据库、文档、搜索工具、API 等连接起来，快速做出 AI 应用。LangChain 官方把它定位为用于构建 Agent 和 LLM 应用的框架，并提供模型、工具、检索等集成能力。</p><p>LangChain里有几个核心概念：</p><ul><li>Model（模型）：需要调用的大模型，例如<code>deepseek-v4</code>、<code>gpt-4</code>、<code>claude</code>等。</li><li>Prompt（提示词）: 高速模型需要做什么指令，Prompt写的越好，模型能力越强。</li><li>Chain（链） ： 可以理解为<code>步骤组合</code>，例如：先整理问题，再查询资料，最后让模型生成答案。</li><li>Tool（工具）: 工具是模型调用的外部力量，例如搜索网页、查询数据、调用接口、运行代码等。</li><li>Agent（智能体）：Agent更像一个会自己判断步骤的AI助手，它可以根据用户任务，决定要不要调用工具，调用哪个工具，下一步做什么。</li><li>Retriever（检索器）：Retriever用来从文档、向量数据库或其他数据源中找出问和问题相关的内容。</li></ul><span id="more"></span><h3 id="LangChain的主要模块"><a href="#LangChain的主要模块" class="headerlink" title="LangChain的主要模块"></a>LangChain的主要模块</h3><ul><li><strong>langchain-core</strong>：轻量核心，包含核心抽象接口（Runnable、BaseMessage等）</li><li><strong>langchain-community</strong>：供应商分离 第三方继承，例如<code>langchain-openai</code>、<code>langchain-deepseek</code>等</li><li><strong>langchain-classic</strong> ：向后兼容，冗余代码或者不推荐使用的api会移动到这里。</li><li><strong>langgraph</strong> ： Agent系统基础，深度整合LangGraph1.0，协调多个Chain、Agent、Tools完成更复杂的任务，并且还支持循环调用，是langchain图形化的增强版</li></ul><p>中文文档：<a href="https://docs.langchain.org.cn/oss/python/deepagents/overview">LangChain 文档 - LangChain 教程</a></p><p>API文档：<a href="https://reference.langchain.com/python/langchain/overview">LangChain - Python API Reference | LangChain Reference</a></p><h3 id="LangChain的生态"><a href="#LangChain的生态" class="headerlink" title="LangChain的生态"></a>LangChain的生态</h3><p>LangChain的生态由四大核心支柱构成：<code>LangChain</code>、<code>LangGraph</code>、<code>Deep Agent</code>、<code>LangSmith</code>。</p><p><img src="https://cdn.dev.net.cn/images/2026/04/langchain-cycle.webp!full" alt="langchain-cycle"></p><h2 id="环境配置与项目"><a href="#环境配置与项目" class="headerlink" title="环境配置与项目"></a>环境配置与项目</h2><ul><li>uv</li><li>rustc</li><li>Python 3.13</li><li>postgresql</li></ul><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><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><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 3.15实测不行。。。</span></span><br><span class="line">uv python install <span class="number">3.13</span></span><br><span class="line">uv python pin <span class="number">3.13</span></span><br><span class="line"><span class="comment"># 创建项目路径</span></span><br><span class="line"><span class="built_in">cd</span> D:\PycharmProjects\ </span><br><span class="line">mkdir langchain<span class="literal">-agent</span></span><br><span class="line"><span class="built_in">cd</span> .\langchain<span class="literal">-agent</span>\</span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始化项目</span></span><br><span class="line">uv init  </span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加依赖</span></span><br><span class="line">uv add <span class="string">&quot;langchain&gt;=1.3.0&quot;</span> <span class="string">&quot;langchain-core&gt;=0.3.0&quot;</span></span><br><span class="line"><span class="comment"># 确保本地rust是最新的，差了两个小版本居然安装出错了 （rustc 1.94 -&gt; rustc 1.96）</span></span><br><span class="line">uv add <span class="string">&quot;langchain-deepseek&quot;</span> <span class="string">&quot;langchain-openai&quot;</span></span><br><span class="line">uv add <span class="string">&quot;langgraph&gt;=1.2.0&quot;</span></span><br><span class="line">uv add <span class="string">&quot;langchain-postgres&gt;=0.0.17&quot;</span></span><br><span class="line">uv add <span class="string">&quot;langchain-community&quot;</span></span><br><span class="line">uv add <span class="string">&quot;httpx&quot;</span> <span class="string">&quot;python-dotenv&quot;</span></span><br><span class="line"></span><br><span class="line">uv add <span class="literal">--dev</span> <span class="string">&quot;pytest&quot;</span> <span class="string">&quot;pytest-asyncio&quot;</span></span><br></pre></td></tr></table></figure><p>依赖说明：</p><ul><li><code>langchain-deepseek</code> 、<code>langchain-openai</code>：目前以DeepSeek和OpenAI为例，有其他的可自行添加，例如：<code>langchain-anthropic</code>、<code>langchain-ollama</code>。</li><li><code>langgraph</code>是底层运行时，<code>agent</code>构建在它的上面。</li><li><code>langchain-postgres</code>提供<code>pgvector</code>向量存储。</li></ul><h3 id="设置环境变量"><a href="#设置环境变量" class="headerlink" title="设置环境变量"></a>设置环境变量</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 确保在项目根目录下</span></span><br><span class="line"><span class="built_in">cd</span> D:\PycharmProjects\langchain<span class="literal">-agent</span></span><br><span class="line"><span class="comment"># 创建.env文件</span></span><br><span class="line">nvim .env</span><br><span class="line"><span class="comment"># 填写如下信息</span></span><br><span class="line">DEEPSEEK_API_KEY=<span class="string">&quot;你的 DEEPSEEK API Key&quot;</span></span><br><span class="line">OPENAI_API_KEY=<span class="string">&quot;你的 OPENAI API Key&quot;</span></span><br></pre></td></tr></table></figure><h3 id="测试一下"><a href="#测试一下" class="headerlink" title="测试一下"></a>测试一下</h3><p>将项目中的<code>main.py</code>，修改为如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">from</span> langchain.chat_models <span class="keyword">import</span> init_chat_model</span><br><span class="line"><span class="keyword">from</span> dotenv <span class="keyword">import</span> load_dotenv</span><br><span class="line"></span><br><span class="line">load_dotenv()</span><br><span class="line">llm = init_chat_model(<span class="string">&quot;deepseek:deepseek-v4-pro&quot;</span>)</span><br><span class="line">result = llm.invoke(<span class="string">&quot;你好，请介绍一下你自己&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(result)</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">content=<span class="string">&#x27;你好呀！很高兴认识你！😊\n\n我是**DeepSeek**，由深度求索公司创造的AI助手。让我简单介绍一下自己的特点：\n\n## 🌟 ......’</span></span><br></pre></td></tr></table></figure><p>如果只使用<code>DeepSeek</code>，可以改成以下代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">from</span> langchain_deepseek <span class="keyword">import</span> ChatDeepSeek</span><br><span class="line"><span class="keyword">from</span> dotenv <span class="keyword">import</span> load_dotenv</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">load_dotenv()</span><br><span class="line">llm = ChatDeepSeek(model=<span class="string">&quot;deepseek-v4-pro&quot;</span>)</span><br><span class="line">res = llm.invoke(<span class="string">&quot;你好，请介绍一下你自己&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(res.content)</span><br></pre></td></tr></table></figure><div class="note info simple"><p>比较推荐使用<code>init_chat_model()</code>，它是LangChain 1.x中推出的用于初始化聊天模型的统一接口。只要是LangChain支持的模型都可以处理，它会根据模型名称自动旋转对应的模型类初始化实例；如果是使用非支持的第三方模型：火山、讯飞等，可以使用<code>ChatOpenAI()</code>。</p></div><h2 id="AI-Agent"><a href="#AI-Agent" class="headerlink" title="AI Agent"></a>AI Agent</h2><h3 id="什么是-AI-Agent"><a href="#什么是-AI-Agent" class="headerlink" title="什么是 AI Agent"></a>什么是 AI Agent</h3><p>Agent是一个<code>能自主感知环境</code>、<code>做出决策</code>、<code>执行行动</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">输入 -&gt; 思考（大模型推理） -&gt; 行动（调用工具/生成回复） -&gt; 观察结果 -&gt; 循环，直到完成</span><br></pre></td></tr></table></figure><h3 id="Agent的核心组件"><a href="#Agent的核心组件" class="headerlink" title="Agent的核心组件"></a>Agent的核心组件</h3><table><thead><tr><th>组件</th><th>职责</th><th>LangChain</th></tr></thead><tbody><tr><td>大脑（LLM）</td><td>推理、决策、生成</td><td><code>BaseChatModel</code></td></tr><tr><td>工具（Tools）</td><td>交互</td><td><code>@tool</code>，<code>BaseTool</code>，<code>Pydantic Schema</code></td></tr><tr><td>记忆（Memory）</td><td>保存对话上下文</td><td><code>checkpointer</code>，<code>BaseStore</code></td></tr><tr><td>规划（Planning）</td><td>分解任务、决定步骤</td><td>Agent loop + middleware</td></tr><tr><td>执行（Exectuion）</td><td>调用工具、处理结果</td><td>LangGraph graph runtime</td></tr></tbody></table><h2 id="RAG"><a href="#RAG" class="headerlink" title="RAG"></a>RAG</h2><p><code>Retrieval-Augmented Generation</code>，检索增强生成。用来解决<code>大模型的知识冻结（语料的时效性）</code>和<code>大模型幻觉（大模型不会的时候，它会胡扯乱编答案）</code></p><p><img src="https://cdn.dev.net.cn/images/2026/04/langchain-chatglm.webp!full" alt="langchain-chatglm"></p><p>大约就是如下的方式：</p><p><img src="https://cdn.dev.net.cn/images/2026/04/langchain-chatglm-01.webp!full" alt="langchain-chatglm-01"></p><h2 id="第一个Agent"><a href="#第一个Agent" class="headerlink" title="第一个Agent"></a>第一个Agent</h2><p>充分利用LLM的推理决策能力，通过增加规划、记忆和工具调用的能力，构造一个能够独立思考、逐步完成给定目标的Agent。<img src="https://cdn.dev.net.cn/images/2026/04/langchain-agent.webp!full" alt="langchain-agent"></p><p><a href="https://lilianweng.github.io/posts/2023-06-23-agent/">LLM Powered Autonomous Agents | Lil’Log</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">from</span> dotenv <span class="keyword">import</span> load_dotenv</span><br><span class="line"><span class="keyword">from</span> langchain.chat_models <span class="keyword">import</span> init_chat_model</span><br><span class="line"></span><br><span class="line"><span class="comment"># 引入 LangChain 相关组件</span></span><br><span class="line"><span class="keyword">from</span> langchain_core.tools <span class="keyword">import</span> tool</span><br><span class="line"><span class="keyword">from</span> langchain_core.prompts <span class="keyword">import</span> ChatPromptTemplate, MessagesPlaceholder</span><br><span class="line"><span class="keyword">from</span> langchain.agents <span class="keyword">import</span> create_agent</span><br><span class="line">load_dotenv()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">模拟数据库</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line">ORDERS = &#123;</span><br><span class="line">    <span class="string">&quot;O1001&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;order_id&quot;</span>: <span class="string">&quot;O1001&quot;</span>,</span><br><span class="line">        <span class="string">&quot;user_id&quot;</span>: <span class="string">&quot;U001&quot;</span>,</span><br><span class="line">        <span class="string">&quot;product&quot;</span>: <span class="string">&quot;无线鼠标&quot;</span>,</span><br><span class="line">        <span class="string">&quot;amount&quot;</span>: <span class="number">199</span>,</span><br><span class="line">        <span class="string">&quot;status&quot;</span>: <span class="string">&quot;delivered&quot;</span>,</span><br><span class="line">        <span class="string">&quot;delivered_at&quot;</span>: <span class="string">&quot;2026-07-01&quot;</span>,</span><br><span class="line">        <span class="string">&quot;payment_status&quot;</span>: <span class="string">&quot;paid&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;O1002&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;order_id&quot;</span>: <span class="string">&quot;O1002&quot;</span>,</span><br><span class="line">        <span class="string">&quot;user_id&quot;</span>: <span class="string">&quot;U001&quot;</span>,</span><br><span class="line">        <span class="string">&quot;product&quot;</span>: <span class="string">&quot;机械键盘&quot;</span>,</span><br><span class="line">        <span class="string">&quot;amount&quot;</span>: <span class="number">599</span>,</span><br><span class="line">        <span class="string">&quot;status&quot;</span>: <span class="string">&quot;delivered&quot;</span>,</span><br><span class="line">        <span class="string">&quot;delivered_at&quot;</span>: <span class="string">&quot;2026-06-10&quot;</span>,</span><br><span class="line">        <span class="string">&quot;payment_status&quot;</span>: <span class="string">&quot;paid&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">REFUND_REQUESTS = &#123;&#125;</span><br><span class="line">SUPPORT_TICKETS = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">编写工具类</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_order</span>(<span class="params">order_id: <span class="built_in">str</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;根据订单号查询订单信息。参数 order_id 格式如 O1001。&quot;&quot;&quot;</span></span><br><span class="line">    order = ORDERS.get(order_id)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> order:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;found&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;message&quot;</span>: <span class="string">&quot;没有找到该订单&quot;</span>&#125;</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;found&quot;</span>: <span class="literal">True</span>, <span class="string">&quot;order&quot;</span>: order&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">check_refund_policy</span>(<span class="params">order_id: <span class="built_in">str</span>, reason: <span class="built_in">str</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;判断订单是否符合退款条件。&quot;&quot;&quot;</span></span><br><span class="line">    order = ORDERS.get(order_id)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> order:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;eligible&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;订单不存在&quot;</span>&#125;</span><br><span class="line">    <span class="keyword">if</span> order[<span class="string">&quot;payment_status&quot;</span>] != <span class="string">&quot;paid&quot;</span>:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;eligible&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;订单未支付，不能退款&quot;</span>&#125;</span><br><span class="line">    <span class="keyword">if</span> order[<span class="string">&quot;status&quot;</span>] != <span class="string">&quot;delivered&quot;</span>:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;eligible&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;订单尚未签收，不能按售后退款处理&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">    delivered_at = datetime.strptime(order[<span class="string">&#x27;delivered_at&#x27;</span>], <span class="string">&quot;%Y-%m-%d&quot;</span>)</span><br><span class="line">    today = datetime.strptime(<span class="string">&quot;2026-07-02&quot;</span>, <span class="string">&quot;%Y-%m-%d&quot;</span>)</span><br><span class="line">    days_since_delivered = (today - delivered_at).days</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> days_since_delivered &lt; <span class="number">7</span>:</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;eligible&quot;</span>: <span class="literal">True</span>,</span><br><span class="line">            <span class="string">&quot;days_since_delivered&quot;</span>: days_since_delivered,</span><br><span class="line">            <span class="string">&quot;policy&quot;</span>: <span class="string">&quot;签收后7天内，质量问题可申请退款&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;eligible&quot;</span>: <span class="literal">False</span>,</span><br><span class="line">        <span class="string">&quot;days_since_delivered&quot;</span>: days_since_delivered,</span><br><span class="line">        <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;已经超过7天，需要转人工客服审核&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_refund_request</span>(<span class="params">order_id: <span class="built_in">str</span>, reason: <span class="built_in">str</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;创建退款申请。注意：该工具不会真正退款，只会创建待确认的退款申请。&quot;&quot;&quot;</span></span><br><span class="line">    order = ORDERS.get(order_id)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> order:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;found&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;message&quot;</span>: <span class="string">&quot;订单不存在，无法创建退款申请&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">    refund_id = <span class="string">f&quot;REF-<span class="subst">&#123;order_id.replace(<span class="string">&#x27;O&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span>&quot;</span></span><br><span class="line">    REFUND_REQUESTS[refund_id] = &#123;</span><br><span class="line">        <span class="string">&quot;refund_id&quot;</span>: refund_id,</span><br><span class="line">        <span class="string">&quot;order_id&quot;</span>: order_id,</span><br><span class="line">        <span class="string">&quot;reason&quot;</span>: reason,</span><br><span class="line">        <span class="string">&quot;amount&quot;</span>: order[<span class="string">&quot;amount&quot;</span>],</span><br><span class="line">        <span class="string">&quot;status&quot;</span>: <span class="string">&quot;pending_user_confirmation&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;created&quot;</span>: <span class="literal">True</span>,</span><br><span class="line">        <span class="string">&quot;refund_request&quot;</span>: REFUND_REQUESTS[refund_id],</span><br><span class="line">        <span class="string">&quot;next_step&quot;</span>: <span class="string">&quot;等待用户或人工客服确认后，才能执行真实退款&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_support_ticket</span>(<span class="params">order_id: <span class="built_in">str</span>, issue: <span class="built_in">str</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;创建人工客服工单，用于处理超出自动化规则的问题。&quot;&quot;&quot;</span></span><br><span class="line">    ticket_id = <span class="string">f&quot;TICKET-<span class="subst">&#123;order_id.replace(<span class="string">&#x27;O&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span>&quot;</span></span><br><span class="line">    SUPPORT_TICKETS[ticket_id] = &#123;</span><br><span class="line">        <span class="string">&quot;order_id&quot;</span>: order_id,</span><br><span class="line">        <span class="string">&quot;issue&quot;</span>: issue,</span><br><span class="line">        <span class="string">&quot;status&quot;</span>: <span class="string">&quot;waiting_for_human_agent&quot;</span>,</span><br><span class="line">        <span class="string">&quot;ticket_id&quot;</span>: ticket_id,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;created&quot;</span>: <span class="literal">True</span>, <span class="string">&quot;ticket&quot;</span>: SUPPORT_TICKETS[ticket_id]&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 整合工具列表</span></span><br><span class="line">tools = [get_order, check_refund_policy, create_refund_request, create_support_ticket]</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">初始化 LangChain 核心组件</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">SYSTEM_PROMPT = <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">你是一个电商售后客服 Agent。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">你的任务：</span></span><br><span class="line"><span class="string">1. 理解用户诉求；</span></span><br><span class="line"><span class="string">2. 如果用户提到订单号，先调用 get_order 查询订单；</span></span><br><span class="line"><span class="string">3. 如果用户要退款，必须调用 check_refund_policy 判断退款资格；</span></span><br><span class="line"><span class="string">4. 如果符合退款条件，只能调用 create_refund_request 创建退款申请；</span></span><br><span class="line"><span class="string">5. 不能声称已经完成真实退款；</span></span><br><span class="line"><span class="string">6. 如果不符合自动退款规则，调用 create_support_ticket 创建人工客服工单；</span></span><br><span class="line"><span class="string">7. 涉及退款、资金、取消订单等高风险动作时，必须明确告诉用户需要确认或人工审核；</span></span><br><span class="line"><span class="string">8. 回复要简洁、清楚、礼貌。</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置大模型（适配 DeepSeek）</span></span><br><span class="line">llm = init_chat_model(</span><br><span class="line">    model=<span class="string">&quot;deepseek:deepseek-v4-pro&quot;</span>,</span><br><span class="line">    <span class="comment"># api_key=os.environ.get(&quot;DEEPSEEK_API_KEY&quot;),</span></span><br><span class="line">    <span class="comment"># base_url=&quot;https://api.deepseek.com&quot;,</span></span><br><span class="line">    temperature=<span class="number">0.0</span>  <span class="comment"># 客服场景建议将温度设为 0，保证严谨度</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 组装 Prompt 模板</span></span><br><span class="line">prompt = ChatPromptTemplate.from_messages([</span><br><span class="line">    (<span class="string">&quot;system&quot;</span>, SYSTEM_PROMPT),</span><br><span class="line">    MessagesPlaceholder(variable_name=<span class="string">&quot;chat_history&quot;</span>), <span class="comment"># 预留给未来可能需要的上下文对话</span></span><br><span class="line">    (<span class="string">&quot;human&quot;</span>, <span class="string">&quot;&#123;input&#125;&quot;</span>),</span><br><span class="line">    MessagesPlaceholder(variable_name=<span class="string">&quot;agent_scratchpad&quot;</span>), <span class="comment"># LangChain 存放中间思考和工具结果的占位符</span></span><br><span class="line">])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 Agent 链</span></span><br><span class="line">agent_executor = create_agent(</span><br><span class="line">    model=llm,</span><br><span class="line">    tools=tools,</span><br><span class="line">    system_prompt=SYSTEM_PROMPT,</span><br><span class="line">    <span class="comment"># max_iterations=5  # 如果需要限制步数可以加上</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">业务统一调用接口</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run_agent</span>(<span class="params">user_message: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        response = agent_executor.invoke(&#123;</span><br><span class="line">            <span class="string">&quot;messages&quot;</span>: [(<span class="string">&quot;human&quot;</span>, user_message)]</span><br><span class="line">        &#125;)</span><br><span class="line">        <span class="comment"># 新版返回的对象中，最后的回复在 messages 列表的最后一条</span></span><br><span class="line">        <span class="keyword">return</span> response[<span class="string">&quot;messages&quot;</span>][-<span class="number">1</span>].content</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;抱歉，这个问题处理步骤较多，我已经停止自动处理，请转人工客服继续处理。&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    user_message = <span class="string">&quot;我的订单O1001,昨天收到的无线鼠标坏了，我想退款&quot;</span></span><br><span class="line"></span><br><span class="line">    reply = run_agent(user_message)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;\n ===== Agent回复 ====\n&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(reply)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;\n == 当前退款申请表 === \n&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(json.dumps(REFUND_REQUESTS, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>))</span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;\n ==== 当前人工工单表 === \n&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(json.dumps(SUPPORT_TICKETS, ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</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><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"> ===== Agent回复 ====</span><br><span class="line"></span><br><span class="line">退款申请已为您创建，以下是详情：</span><br><span class="line"></span><br><span class="line">| 项目 | 内容 |</span><br><span class="line">|------|------|</span><br><span class="line">| **退款编号** | REF-1001 |</span><br><span class="line">| **订单号** | O1001 |</span><br><span class="line">| **退款金额** | ¥199.00 |</span><br><span class="line">| **原因** | 收到的无线鼠标坏了 |</span><br><span class="line">| **状态** | ⚠️ 待确认 |</span><br><span class="line"></span><br><span class="line">需要提醒您：**当前退款申请尚未生效**，还需要您确认或人工客服审核后才能真正执行退款。请您留意后续通知，或前往「我的订单」中确认该退款申请。</span><br><span class="line"></span><br><span class="line">请问还有其他需要帮您处理的吗？</span><br><span class="line"></span><br><span class="line"> == 当前退款申请表 === </span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;REF-1001&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;refund_id&quot;</span>: <span class="string">&quot;REF-1001&quot;</span>,</span><br><span class="line">    <span class="string">&quot;order_id&quot;</span>: <span class="string">&quot;O1001&quot;</span>,</span><br><span class="line">    <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;收到的无线鼠标坏了&quot;</span>,</span><br><span class="line">    <span class="string">&quot;amount&quot;</span>: 199,</span><br><span class="line">    <span class="string">&quot;status&quot;</span>: <span class="string">&quot;pending_user_confirmation&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><br><span class="line"></span><br><span class="line">&#123;&#125;</span><br></pre></td></tr></table></figure><p>这样，一个简单的AI Agent就完成了。</p>]]>
    </content>
    <id>https://dev.net.cn/learning-langchain-and-langgraph-01/</id>
    <link href="https://dev.net.cn/learning-langchain-and-langgraph-01/"/>
    <published>2026-07-05T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="LangChain介绍"><a href="#LangChain介绍" class="headerlink" title="LangChain介绍"></a>LangChain介绍</h2><p>LangChain 是一个用来开发大模型应用的开源框架。简单说，它可以帮助我们把大语言模型，比如 DeepSeek、OpenAI、Claude 等，和自己的程序、数据库、文档、搜索工具、API 等连接起来，快速做出 AI 应用。LangChain 官方把它定位为用于构建 Agent 和 LLM 应用的框架，并提供模型、工具、检索等集成能力。</p>
<p>LangChain里有几个核心概念：</p>
<ul>
<li>Model（模型）：需要调用的大模型，例如<code>deepseek-v4</code>、<code>gpt-4</code>、<code>claude</code>等。</li>
<li>Prompt（提示词）: 高速模型需要做什么指令，Prompt写的越好，模型能力越强。</li>
<li>Chain（链） ： 可以理解为<code>步骤组合</code>，例如：先整理问题，再查询资料，最后让模型生成答案。</li>
<li>Tool（工具）: 工具是模型调用的外部力量，例如搜索网页、查询数据、调用接口、运行代码等。</li>
<li>Agent（智能体）：Agent更像一个会自己判断步骤的AI助手，它可以根据用户任务，决定要不要调用工具，调用哪个工具，下一步做什么。</li>
<li>Retriever（检索器）：Retriever用来从文档、向量数据库或其他数据源中找出问和问题相关的内容。</li>
</ul>]]>
    </summary>
    <title>LangChain、LangGraph学习笔记 - 初识庐山真面目</title>
    <updated>2026-07-05T02: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>今天开始学习数据库操作：</p><ul><li>database&#x2F;sql：标准库，偏底层，显示SQL</li><li>GORM： ORM 开发效率高，适合快速CRUD</li></ul><span id="more"></span><h2 id="database-sql"><a href="#database-sql" class="headerlink" title="database&#x2F;sql"></a>database&#x2F;sql</h2><p><code>database/sql</code>是Go标准库提供的通用SQL数据库接口，需要配合具体数据库<code>driver</code>使用。</p><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">go get github.com/go-sql-driver/mysql</span><br></pre></td></tr></table></figure><h3 id="创建pkg-db-db-go文件"><a href="#创建pkg-db-db-go文件" class="headerlink" title="创建pkg/db/db.go文件"></a>创建<code>pkg/db/db.go</code>文件</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> db</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;database/sql&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">_ <span class="string">&quot;github.com/go-sql-driver/mysql&quot;</span>  <span class="comment">// 注意这里，忘记引用的话会报错</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">NewDB</span><span class="params">(dsn <span class="type">string</span>)</span></span> (*sql.DB, <span class="type">error</span>) &#123;</span><br><span class="line">db, err := sql.Open(<span class="string">&quot;mysql&quot;</span>, dsn)</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">db.SetMaxOpenConns(<span class="number">20</span>)</span><br><span class="line">db.SetMaxIdleConns(<span class="number">10</span>)</span><br><span class="line">db.SetConnMaxLifetime(time.Hour)</span><br><span class="line">    <span class="comment">// sql.Open不一定马上建立真实连接，需要执行一次Ping()命令才能建立真实链接，并检测是否可用。</span></span><br><span class="line"><span class="keyword">if</span> err := db.Ping(); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line">log.Println(<span class="string">&quot;pong....&quot;</span>)</span><br><span class="line"><span class="keyword">return</span> db, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>sql.DB</code>并不是一个链接，而是数据库连接池的句柄，内部管理多个链接，应该在应用启动时创建一次并复用，而不是每次请求都创建。</p></div><h3 id="创建表结构"><a href="#创建表结构" class="headerlink" title="创建表结构"></a>创建表结构</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">CREATE TABLE</span> users (</span><br><span class="line">  id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  name <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  email <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span>,</span><br><span class="line">  age <span class="type">INT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  password <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span>,</span><br><span class="line">  updated_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="编写repository"><a href="#编写repository" class="headerlink" title="编写repository"></a>编写repository</h3><p><code>internal/repository/user_repository.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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> repository</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;context&quot;</span></span><br><span class="line"><span class="string">&quot;database/sql&quot;</span></span><br><span class="line"><span class="string">&quot;errors&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;sync&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/errs&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/model&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">mu    sync.RWMutex</span><br><span class="line">users <span class="keyword">map</span>[<span class="type">int</span>]*model.User</span><br><span class="line">db    *sql.DB</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">NewUserRepository</span><span class="params">(db *sql.DB)</span></span> *UserRepository &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserRepository&#123;</span><br><span class="line">users: <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">int</span>]*model.User),</span><br><span class="line">db:    db,</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">(r *UserRepository)</span></span> FindByID(ctx context.Context, id <span class="type">int</span>) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">query := <span class="string">`</span></span><br><span class="line"><span class="string">select id,name,age,email,password,create_at,update_at from users where id = ?</span></span><br><span class="line"><span class="string">`</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> user model.User</span><br><span class="line">err := r.db.QueryRowContext(ctx, query, id).Scan(</span><br><span class="line">&amp;user.ID,</span><br><span class="line">&amp;user.Name,</span><br><span class="line">&amp;user.Age,</span><br><span class="line">&amp;user.Email,</span><br><span class="line">&amp;user.Status,</span><br><span class="line">&amp;user.CreateAt,</span><br><span class="line">&amp;user.UpdateAt,</span><br><span class="line">)</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> errors.Is(err, sql.ErrNoRows) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, errs.ErrUserNotFound</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> &amp;user, <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="params">(r *UserRepository)</span></span> Create(ctx context.Context, user *model.User) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">query := <span class="string">`</span></span><br><span class="line"><span class="string">insert into users(name,email,age,password,status,create_at,update_at) values (?,?,?,?,?,?,?)</span></span><br><span class="line"><span class="string">`</span></span><br><span class="line"></span><br><span class="line">result, err := r.db.ExecContext(</span><br><span class="line">ctx,</span><br><span class="line">query,</span><br><span class="line">user.Name,</span><br><span class="line">user.Email,</span><br><span class="line">user.Age,</span><br><span class="line">user.Password,</span><br><span class="line">user.Status,</span><br><span class="line">user.CreateAt,</span><br><span class="line">user.UpdateAt,</span><br><span class="line">)</span><br><span class="line"></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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">id, err := result.LastInsertId()</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line">user.ID = <span class="type">int</span>(id)</span><br><span class="line">log.Println(<span class="string">&quot;mysql ....&quot;</span>)</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="function"><span class="keyword">func</span> <span class="params">(r *UserRepository)</span></span> ListUser(ctx context.Context) ([]*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">query := <span class="string">`select id,name,email,age,password,status,create_at,update_at from users`</span></span><br><span class="line"></span><br><span class="line">rows, err := r.db.QueryContext(ctx, query)</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">QueryContext执行后，需要关闭，避免因异常导致持续占有链接，导致资源耗尽。</span></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">()</span></span> &#123;</span><br><span class="line">err := rows.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">&quot;查询关闭失败 ： %v&quot;</span>, 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="keyword">var</span> users []*model.User</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> rows.Next() &#123;</span><br><span class="line"><span class="keyword">var</span> u model.User</span><br><span class="line"></span><br><span class="line">err := rows.Scan(</span><br><span class="line">&amp;u.ID,</span><br><span class="line">&amp;u.Name,</span><br><span class="line">&amp;u.Email,</span><br><span class="line">&amp;u.Age,</span><br><span class="line">&amp;u.Password,</span><br><span class="line">&amp;u.Status,</span><br><span class="line">&amp;u.CreateAt,</span><br><span class="line">&amp;u.UpdateAt,</span><br><span class="line">)</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line">users = <span class="built_in">append</span>(users, &amp;u)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">这里需要补一个Err()检查，因为rows.Next()返回false退出循环后，有可能是没数据了，也有可能时网络传输出现异常，</span></span><br><span class="line"><span class="comment">为了区分这个问题，就需要做一次rows.Err()检查</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">if</span> err = rows.Err(); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> users, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注意，请使用带<code>Context</code>的方法：<code>ExecContext</code>、<code>QueryContext</code>、<code>QueryRowContext</code>，这样请求取消或者超时时，数据库也有机会取消。</p><div class="note info simple"><p><code>QueryRow</code>用于查询单行结果，通常配合<code>Scan</code>；<code>Query</code>用于查询多行结果，需要遍历<code>rows.Next()</code>，最后检查<code>rows.Err()</code></p></div><p>注意点1:</p><div class="note info simple"><p>对于QueryContext，查询完成后需要<code>rows.Close()</code>，因为它执行后会从底层得连接池<code>sql.DB</code>中独占一个有效的数据库链接，知道<code>rows</code>的数据全部读完。如果你得代码在<code>rows.Next()</code>循环中因为某种原因提前<code>return</code>，或者逻辑问题没处理完且没有调用<code>Close()</code>，那么这个TCP连接将会被永远挂起，无法放回连接池中。一单达到<code>SetMaxOpenConns</code>得上线就会彻底卡死。</p></div><p>注意点2:</p><div class="note info simple"><p>循环完成后，还需要补一个<code>rows.Err()</code>检查。避免读取某一行是因网络波动发生异常，为了区分是读取完数据，还是发生异常，这里就需要在循环外再调用<code>rows.Err()</code>进行二次确认。避免将不完整得数据返回给上层。</p></div><p>注意点3:</p><div class="note info simple"><p>还需要注意的是<code>NULL</code>值，Go语言的<code>int</code>、<code>string</code>等类型不能接收数据库得NULL值，所以建表的时候必须加上<code>NOT NULL DEFAULT &#39;&#39;</code>或者<code>DEFAULT 0</code>,如果表里就是有<code>NULL</code>,那就需要标准库自带的<code>sql.NullInt32</code>、<code>sql.NullString</code>，或者将字段定义为指针类型（Age  *int），这样遇到<code>NULL</code>时，Go会自动赋值为<code>nil</code></p></div><h3 id="修改service"><a href="#修改service" class="headerlink" title="修改service"></a>修改service</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> service</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;context&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/internal/repository&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/model&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserService <span class="keyword">struct</span> &#123;</span><br><span class="line">userRepo *repository.UserRepository</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">(userRepo *repository.UserRepository)</span></span> *UserService &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserService&#123;</span><br><span class="line">userRepo: userRepo,</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> CreateUserInput <span class="keyword">struct</span> &#123;</span><br><span class="line">Name     <span class="type">string</span></span><br><span class="line">Email    <span class="type">string</span></span><br><span class="line">Age      <span class="type">int</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="function"><span class="keyword">func</span> <span class="params">(s *UserService)</span></span> CreateUser(ctx context.Context, input CreateUserInput) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">user := &amp;model.User&#123;</span><br><span class="line">Name:     input.Name,</span><br><span class="line">Email:    input.Email,</span><br><span class="line">Age:      input.Age,</span><br><span class="line">Password: input.Password,</span><br><span class="line">Status:   model.UserStatusActive,</span><br><span class="line">CreateAt: time.Now(),</span><br><span class="line">UpdateAt: time.Now(),</span><br><span class="line">&#125;</span><br><span class="line">log.Println(<span class="string">&quot;service ....&quot;</span>)</span><br><span class="line"><span class="comment">//调用repository</span></span><br><span class="line"><span class="keyword">return</span> s.userRepo.Create(ctx, 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> FindUserByID(ctx context.Context, id <span class="type">int</span>) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">user, err := s.userRepo.FindByID(ctx, id)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">&quot;查询用户失败: %v&quot;</span>, err)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</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="function"><span class="keyword">func</span> <span class="params">(s *UserService)</span></span> ListUser(ctx context.Context) ([]*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">users, err := s.userRepo.ListUser(ctx)</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> users, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="修改handler"><a href="#修改handler" class="headerlink" title="修改handler"></a>修改handler</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> handler</span><br><span class="line"></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;strings&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/errs&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/internal/service&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/model&quot;</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="keyword">type</span> UserHandler <span class="keyword">struct</span> &#123;</span><br><span class="line">userService *service.UserService</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">NewUserHandler</span><span class="params">(userService *service.UserService)</span></span> *UserHandler &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserHandler&#123;</span><br><span class="line">userService: userService,</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">(h *UserHandler)</span></span> GetUserListHandler(c *gin.Context) &#123;</span><br><span class="line">page, err := strconv.Atoi(c.DefaultQuery(<span class="string">&quot;page&quot;</span>, <span class="string">&quot;1&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> 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">pageSize, err := strconv.Atoi(c.DefaultQuery(<span class="string">&quot;page_size&quot;</span>, <span class="string">&quot;10&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> 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">users, err := h.userService.ListUser(c.Request.Context())</span><br><span class="line"><span class="keyword">if</span> 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.PageOK(c, users, page, pageSize)</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">(h *UserHandler)</span></span> GetUserInfoByIdHandler(c *gin.Context) &#123;</span><br><span class="line">id, err := strconv.Atoi(c.Param(<span class="string">&quot;id&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">response.BadRequest(c, <span class="string">&quot;id must be number&quot;</span>)</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> id &lt; <span class="number">0</span> &#123;</span><br><span class="line">response.BadRequest(c, <span class="string">&quot;id is null&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">user, err := h.userService.FindUserByID(c.Request.Context(), id)</span><br><span class="line"></span><br><span class="line">response.OK(c, 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">(h *UserHandler)</span></span> CreateUserHandler(c *gin.Context) &#123;</span><br><span class="line"><span class="keyword">var</span> user model.CreateUserReq</span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;user); 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 class="keyword">if</span> err := validateEmail(user.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">user1, err := h.userService.CreateUser(c.Request.Context(), service.CreateUserInput&#123;</span><br><span class="line">Name:     user.Name,</span><br><span class="line">Age:      user.Age,</span><br><span class="line">Email:    user.Email,</span><br><span class="line">Password: user.Password,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> 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">res := model.UserResponse&#123;</span><br><span class="line">ID:    <span class="number">1</span>,</span><br><span class="line">Name:  user1.Name,</span><br><span class="line">Age:   user1.Age,</span><br><span class="line">Email: user1.Email,</span><br><span class="line">&#125;</span><br><span class="line">response.OK(c, res)</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">UpdateUserHandler</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">response.BadRequest(c, <span class="string">&quot;id is null&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 模拟根据ID获取用户</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> user model.CreateUserReq</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;user); err != <span class="literal">nil</span> &#123;</span><br><span class="line">response.BadRequest(c, err.Error())</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">res := model.UserResponse&#123;</span><br><span class="line">ID:    <span class="number">1</span>,</span><br><span class="line">Name:  user.Name,</span><br><span class="line">Age:   user.Age,</span><br><span class="line">Email: user.Email,</span><br><span class="line">&#125;</span><br><span class="line">response.OK(c, res)</span><br><span class="line"></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">DeleteUserByIdHandler</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">response.BadRequest(c, <span class="string">&quot;id is null&quot;</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">response.OK(c, <span class="string">&quot;用户已被删除&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// FormLogin /*</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">FormLogin</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">username := c.PostForm(<span class="string">&quot;username&quot;</span>)</span><br><span class="line">password := c.PostForm(<span class="string">&quot;password&quot;</span>)</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;username&quot;</span>: username,</span><br><span class="line"><span class="string">&quot;password&quot;</span>: password,</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">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">if</span> strings.HasSuffix(email, <span class="string">&quot;@qq.com&quot;</span>) &#123;</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 class="keyword">return</span> errs.ErrEmailExists</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="修改router-go"><a href="#修改router-go" class="headerlink" title="修改router.go"></a>修改router.go</h3><p>修改<code>router.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></pre></td><td class="code"><pre><span class="line">userRepo := repository.NewUserRepository(db)</span><br><span class="line"></span><br><span class="line">d1Group := r.Group(<span class="string">&quot;/api/d1/&quot;</span>)</span><br><span class="line">&#123;</span><br><span class="line">d1Group.GET(<span class="string">&quot;/users&quot;</span>, userHandler.GetUserListHandler)</span><br><span class="line">d1Group.GET(<span class="string">&quot;/users/:id&quot;</span>, userHandler.GetUserInfoByIdHandler)</span><br><span class="line">d1Group.POST(<span class="string">&quot;/users&quot;</span>, userHandler.CreateUserHandler)</span><br><span class="line">d1Group.PUT(<span class="string">&quot;/users/:id&quot;</span>, handler.UpdateUserHandler)</span><br><span class="line">d1Group.DELETE(<span class="string">&quot;users/:id&quot;</span>, handler.DeleteUserByIdHandler)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="修改main-go"><a href="#修改main-go" class="headerlink" title="修改main.go"></a>修改main.go</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">dsn := <span class="string">&quot;root:123456@tcp(127.0.0.1:3306)/goweb?charset=utf8mb4&amp;parseTime=True&amp;loc=Local&quot;</span></span><br><span class="line"></span><br><span class="line">dbInstance, err := db.NewDB(dsn)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">&quot;数据库连接失败: %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">log.Println(<span class="string">&quot;数据库连接池初始化成功...&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">err := dbInstance.Close()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">&quot;数据库关闭失败: %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line">r := routers.SetupRouter(dbInstance)</span><br></pre></td></tr></table></figure><h2 id="GORM"><a href="#GORM" class="headerlink" title="GORM"></a>GORM</h2><p>应该是类似于Java的<code>Hibernate</code>。</p><h3 id="安装依赖-1"><a href="#安装依赖-1" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">go get gorm.io/gorm</span><br><span class="line">go get gorm.io/driver/mysql</span><br></pre></td></tr></table></figure><h3 id="GORM-VS-database-sql"><a href="#GORM-VS-database-sql" class="headerlink" title="GORM VS database&#x2F;sql"></a>GORM VS database&#x2F;sql</h3><table><thead><tr><th>操作</th><th>原生</th><th>GORM</th></tr></thead><tbody><tr><td>插入</td><td>db.Exec(“INSERT …”)</td><td>db.Create(&amp;user)</td></tr><tr><td>查询</td><td>db.QueryRow(“SELECT …”)</td><td>db.First(&amp;user,1)</td></tr><tr><td>更新</td><td>db.Exec(“UPDATE ….”)</td><td>db.Model(&amp;user).Update(…)</td></tr><tr><td>删除</td><td>db.Exec(“DELETE …”)</td><td>db.Delete(&amp;user)</td></tr></tbody></table><p>就像<code>Hibernate</code>一样，不用写SQL就可以完成绝大多数的<code>CRUD</code>场景。</p><h3 id="定义模型"><a href="#定义模型" class="headerlink" title="定义模型"></a>定义模型</h3><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><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">    ID       <span class="type">int</span>    <span class="string">`gorm:&quot;primaryKey&quot;`</span></span><br><span class="line">Name     <span class="type">string</span> <span class="string">`gorm:&quot;size:50;not null&quot;`</span></span><br><span class="line">Email    <span class="type">string</span> <span class="string">`gorm:&quot;size:100;not null&quot;`</span></span><br><span class="line">Age      <span class="type">int</span>    <span class="string">`gorm:&quot;not null&quot;`</span></span><br><span class="line">    Password <span class="type">string</span> <span class="string">`gorm:&quot;type:varchar(255);not null&quot;`</span></span><br><span class="line">Status   <span class="type">string</span> <span class="string">`gorm:&quot;size:20;not null&quot;`</span></span><br><span class="line">CreateAt time.Time</span><br><span class="line">UpdateAt time.Time</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>User</code>结构体名称默认会被转换为 数据库的表名 <code>users</code>，这里会带个<code>s</code>。</p><p>等同于如下SQL</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">CREATE TABLE</span> users (</span><br><span class="line">    id         <span class="type">INT</span>         <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    name       <span class="type">varchar</span>(<span class="number">50</span>)  <span class="keyword">NOT NULL</span>,</span><br><span class="line">    email      <span class="type">varchar</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    age        <span class="type">int</span>          <span class="keyword">NOT NULL</span>,</span><br><span class="line">    password   <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    status     <span class="type">varchar</span>(<span class="number">20</span>)  <span class="keyword">NOT NULL</span>,</span><br><span class="line">    create_at  DATETIME,</span><br><span class="line">update_at  DATATIME</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>从这里可以注意到，<code>GORM</code>使用了和<code>JSON</code>一样的方式，结构体标签：<code>gorm:&quot;primaryKey&quot;</code>。</p><h3 id="结构体标签"><a href="#结构体标签" class="headerlink" title="结构体标签"></a>结构体标签</h3><p>常用的标签有：</p><table><thead><tr><th>标签</th><th>含义</th><th>示例</th><th>SQL</th></tr></thead><tbody><tr><td>type</td><td>指定类型</td><td><code>gorm:&quot;type:varchar(100)&quot;</code></td><td><code>email varchar(100)</code></td></tr><tr><td>not null</td><td>非空约束</td><td><code>gorm::&quot;not null&quot;</code></td><td><code>status varchar(20) NOT NULL</code></td></tr><tr><td>uniqueIndex</td><td>唯一索引</td><td><code>gorm:&quot;uniqueIndex&quot;</code></td><td><code>UNIQUE KEY </code>idx_users_mobile<code> (</code>mobile<code>) </code></td></tr><tr><td>index</td><td>普通索引&#x2F;联合索引</td><td><code>gorm:&quot;index&quot;</code></td><td>KEY <code>idx_users_name</code> (<code>name</code>),</td></tr><tr><td>default</td><td>默认值</td><td><code>gorm:&quot;default:aaa</code></td><td><code>xxx varchar NOT NULL DEFAULT aaa</code></td></tr><tr><td>column</td><td>自定义列名</td><td><code>gorm:&quot;column:age&quot;</code></td><td><code>age INT</code></td></tr><tr><td><code>primaryKey</code></td><td>指定主键</td><td><code>gorm:&quot;primaryKey&quot;</code></td><td><code>ID uint gorm:&quot;primaryKey&quot;</code></td></tr><tr><td>autoIncrement</td><td>自增</td><td>gorm:”primaryKey;autoIncrement”</td><td>NOT NULL AUTO_INCREMENT</td></tr><tr><td>-</td><td>忽略字段</td><td>gorm:”-“</td><td>该字段不体现在SQL中</td></tr><tr><td>size</td><td>大小限制</td><td>gorm:”size:255”</td><td>varchar(255)</td></tr></tbody></table><p>其他的还有<code>foreignKey</code>，<code>references</code>，包括嵌入结构体<code>embedded</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> model</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line"><span class="string">&quot;gorm.io/gorm&quot;</span></span><br><span class="line">)</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">ID        <span class="type">int64</span>          <span class="string">`gorm:&quot;primaryKey;autoIncrement;comment:主键ID&quot;`</span></span><br><span class="line">Username  <span class="type">string</span>         <span class="string">`gorm:&quot;type:varchar(50);not null;uniqueIndex;comment:用户名&quot;`</span></span><br><span class="line">Password  <span class="type">string</span>         <span class="string">`gorm:&quot;type:varchar(255);not null;comment:密码哈希&quot;`</span></span><br><span class="line">Nickname  <span class="type">string</span>         <span class="string">`gorm:&quot;type:varchar(50);not null;default:&#x27;&#x27;;comment:昵称&quot;`</span></span><br><span class="line">Age       <span class="type">int</span>            <span class="string">`gorm:&quot;type:int;not null;default:0;index;comment:年龄&quot;`</span></span><br><span class="line">Email     <span class="type">string</span>         <span class="string">`gorm:&quot;type:varchar(100);not null;default:&#x27;&#x27;;comment:邮箱&quot;`</span></span><br><span class="line">Status    tinyint        <span class="string">`gorm:&quot;type:tinyint;not null;default:1;comment:状态:1=正常,2=禁用&quot;`</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 审计字段</span></span><br><span class="line">CreatedAt time.Time      <span class="string">`gorm:&quot;column:created_at;not null;comment:创建时间&quot;`</span></span><br><span class="line">UpdatedAt time.Time      <span class="string">`gorm:&quot;column:updated_at;not null;comment:更新时间&quot;`</span></span><br><span class="line">DeletedAt gorm.DeletedAt <span class="string">`gorm:&quot;index;comment:删除时间(软删除标记)&quot;`</span> <span class="comment">// 开启软删除</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 业务缓存字段</span></span><br><span class="line">LoginToken <span class="type">string</span>        <span class="string">`gorm:&quot;-&quot;`</span> <span class="comment">// 不入库</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行<code>db.AutoMigrate(&amp;User&#123;&#125;)</code>，生成如下语句</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">CREATE TABLE</span> `users` (</span><br><span class="line">  `id` <span class="type">bigint</span> <span class="keyword">NOT NULL</span> AUTO_INCREMENT COMMENT <span class="string">&#x27;主键ID&#x27;</span>,</span><br><span class="line">  `username` <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;用户名&#x27;</span>,</span><br><span class="line">  `password` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;密码哈希&#x27;</span>,</span><br><span class="line">  `nickname` <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;&#x27;</span> COMMENT <span class="string">&#x27;昵称&#x27;</span>,</span><br><span class="line">  `age` <span class="type">int</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;0&#x27;</span> COMMENT <span class="string">&#x27;年龄&#x27;</span>,</span><br><span class="line">  `email` <span class="type">varchar</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;&#x27;</span> COMMENT <span class="string">&#x27;邮箱&#x27;</span>,</span><br><span class="line">  `status` tinyint <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;1&#x27;</span> COMMENT <span class="string">&#x27;状态:1=正常,2=禁用&#x27;</span>,</span><br><span class="line">  `created_at` datetime(<span class="number">3</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">  `updated_at` datetime(<span class="number">3</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">  `deleted_at` datetime(<span class="number">3</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;删除时间(软删除标记)&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY KEY</span> (`id`),</span><br><span class="line">  <span class="keyword">UNIQUE</span> KEY `idx_users_username` (`username`),</span><br><span class="line">  KEY `idx_users_age` (`age`),</span><br><span class="line">  KEY `idx_users_deleted_at` (`deleted_at`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_0900_ai_ci;</span><br></pre></td></tr></table></figure><h3 id="自定义表名"><a href="#自定义表名" class="headerlink" title="自定义表名"></a>自定义表名</h3><p><code>GORM</code>默认使用结构体的名(s)称作为表名，如果想自定义表名，需要在定义结构体的地方添加如下代码：</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">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">    ID       <span class="type">int</span>    <span class="string">`gorm:&quot;primaryKey&quot;`</span></span><br><span class="line">Name     <span class="type">string</span> <span class="string">`gorm:&quot;size:50;not null&quot;`</span></span><br><span class="line">Email    <span class="type">string</span> <span class="string">`gorm:&quot;size:100;not null&quot;`</span></span><br><span class="line">Age      <span class="type">int</span>    <span class="string">`gorm:&quot;not null&quot;`</span></span><br><span class="line">    Password <span class="type">string</span> <span class="string">`gorm:&quot;type:varchar(255);not null&quot;`</span></span><br><span class="line">Status   <span class="type">string</span> <span class="string">`gorm:&quot;size:20;not null&quot;`</span></span><br><span class="line">CreateAt time.Time</span><br><span class="line">UpdateAt time.Time</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加上这段代码，就可以将表名定义为user_info</span></span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">(User)</span></span> TableName() <span class="type">string</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;user_info&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除了表名规则，还有列名规则：</p><ul><li>Name  -&gt; name</li><li>Email  -&gt; email</li><li>CreateAt -&gt; create_at</li></ul><h3 id="时间戳"><a href="#时间戳" class="headerlink" title="时间戳"></a>时间戳</h3><p>从上面的izi可以看到，对于时间字段，并没有使用结构体标签，因为<code>GORM</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></pre></td><td class="code"><pre><span class="line">DeletedAt  gorm.DeletedAt <span class="string">`gorm:&quot;index&quot;`</span></span><br></pre></td></tr></table></figure><p>此时<code>GORM</code>就会支持软删除，所谓软删除就是不真删除数据，只是给<code>deleted_at</code>字段设个时间戳，查询的时候<code>GORM</code>会自动过略掉<code>已删除</code>的记录。</p><h3 id="自动建表"><a href="#自动建表" class="headerlink" title="自动建表"></a>自动建表</h3><p>代码执行<code>db.AutoMigrate(&amp;User&#123;&#125;)</code>就会自动创建<code>user</code>的表：</p><ul><li>检测表是否存在，不存在就创建</li><li>检查字段是否完整，缺少的列自动补充</li><li>检测索引是否正确</li></ul><div class="note info simple"><p><code>AutoMigrate</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></pre></td><td class="code"><pre><span class="line">db.Migrator().HasTable(&amp;User&#123;&#125;) &#123;</span><br><span class="line">    <span class="comment">// 表已存在</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>对于生产环境，更推荐<code>migration</code>工具：<code>golang-migrate</code>、<code>goose</code>、<code>atlas</code>。 因为正式环境的数据库变更需要可审计、可回滚、可灰度，不能依赖启动时自动修改表结构。</p></div><h3 id="增删改查"><a href="#增删改查" class="headerlink" title="增删改查"></a>增删改查</h3><ul><li>Create   : 新增</li><li>First  : 查询单条数据</li><li>Find : 查询多条数据</li><li>Where : 条件查询</li><li>Updates ： 更新多个字段</li><li>Update : 更新单个字段</li><li>Delete  ： 删除</li><li>Unscoped : 物理删除&#x2F;查询软删除数据</li><li>Transaction  : 事务</li></ul><h4 id="Create"><a href="#Create" class="headerlink" title="Create"></a>Create</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">(r *goodsRepository)</span></span> Create(ctx context.Context, goods *model.Goods) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> r.db.WithContext(ctx).Create(goods).Error</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"><span class="function"><span class="keyword">func</span> <span class="params">(r *goodsRepository)</span></span> BatchCreate(ctx context.Context, goods []*model.Goods) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(goods) == <span class="number">0</span> &#123;</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 class="keyword">if</span> err := r.db.WithContext(ctx).Create(&amp;goods).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;批量保存商品失败： %w&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 数据量大，使用CreateInBatches方法分批插入，避免超过SQL长度限制</span></span><br><span class="line"><span class="comment">//if err := r.db.WithContext(ctx).CreateInBatches(&amp;goods, 100).Error; err != nil &#123;</span></span><br><span class="line"><span class="comment">//return fmt.Errorf(&quot;批量保存商品失败： %w&quot;, err)</span></span><br><span class="line"><span class="comment">//&#125;</span></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><h4 id="First"><a href="#First" class="headerlink" title="First"></a>First</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="comment">// FindGoodsByID First，如果找不到数据，会返回一个gorm.ErrRecordNotFound的错误，Service需要处理。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *goodsRepository)</span></span> FindGoodsByID(ctx context.Context, id <span class="type">int64</span>) (*model.Goods, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> goods model.Goods</span><br><span class="line"><span class="keyword">if</span> err := r.db.WithContext(ctx).First(&amp;goods, id).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;goods, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于Service层</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">goods, err := s.goodsRepository.FindGoodsByID(ctx, <span class="number">1001</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">if</span> errors.Is(err, gorm.ErrRecordNotFound) &#123;</span><br><span class="line">        <span class="comment">// 说明没有这个商品，这属于正常业务逻辑，不该当成服务器崩溃处理</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span>, errors.New(<span class="string">&quot;商品不存在&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">return</span> <span class="literal">nil</span>, err </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Find"><a href="#Find" class="headerlink" title="Find"></a>Find</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *goodsRepository)</span></span> ListGoods(ctx context.Context) ([]*model.Goods, <span class="type">error</span>) &#123;</span><br><span class="line">    goods := <span class="built_in">make</span>([]*model.Goods, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> err := r.db.WithContext(ctx).Find(&amp;goods).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> goods, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Where"><a href="#Where" class="headerlink" title="Where"></a>Where</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><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="params">(r *goodsRepository)</span></span> FindGoodsByIDAndPrice(ctx context.Context, id <span class="type">int64</span>, price <span class="type">int</span>) (*model.Goods, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> goods model.Goods</span><br><span class="line"><span class="keyword">if</span> err := r.db.WithContext(ctx).Where(<span class="string">&quot;price = ?&quot;</span>, price).First(&amp;goods, id).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;goods, <span class="literal">nil</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ListGoodsInput <span class="keyword">struct</span> &#123;</span><br><span class="line">Keyword  <span class="type">string</span></span><br><span class="line">Status   <span class="type">string</span></span><br><span class="line">Page     <span class="type">int</span></span><br><span class="line">PageSize <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Where + Find + Count + Limit + Offset</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *goodsRepository)</span></span> FindGoodsByMultiCondition(ctx context.Context, input ListGoodsInput) ([]model.Goods, <span class="type">int64</span>, <span class="type">error</span>) &#123;</span><br><span class="line">goods := <span class="built_in">make</span>([]model.Goods, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">var</span> total <span class="type">int64</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> input.Page &lt;= <span class="number">0</span> &#123;</span><br><span class="line">input.Page = <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> input.PageSize &lt;= <span class="number">0</span> || input.PageSize &gt; <span class="number">100</span> &#123;</span><br><span class="line">input.PageSize = <span class="number">20</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">query := r.db.WithContext(ctx).Model(&amp;model.Goods&#123;&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> input.Keyword != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">keyword := <span class="string">&quot;%&quot;</span> + input.Keyword + <span class="string">&quot;%&quot;</span></span><br><span class="line">query = query.Where(<span class="string">&quot;name LIKE ? OR code LIKE ?&quot;</span>, keyword, keyword)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> input.Status != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">query = query.Where(<span class="string">&quot;status = ?&quot;</span>, input.Status)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//使用 Session(&amp;gorm.Session&#123;&#125;) 创建一个干净的克隆会话来执行 Count</span></span><br><span class="line"><span class="comment">// 这样可以确保 Count 的执行状态绝对不会污染后面 Find 的 SQL 语句</span></span><br><span class="line"><span class="keyword">if</span> err := query.Session(&amp;gorm.Session&#123;&#125;).Count(&amp;total).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, <span class="number">0</span>, fmt.Errorf(<span class="string">&quot;统计商品数失败 ： %w&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> total == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> goods, <span class="number">0</span>, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">offset := (input.Page - <span class="number">1</span>) * input.PageSize</span><br><span class="line"><span class="keyword">if</span> err := query.Order(<span class="string">&quot;id DESC&quot;</span>).Limit(input.PageSize).Offset(offset).Find(&amp;goods).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, <span class="number">0</span>, fmt.Errorf(<span class="string">&quot;查询商品失败 ： %w&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> goods, total, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Updates"><a href="#Updates" class="headerlink" title="Updates"></a>Updates</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><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="params">(r *goodsRepository)</span></span> Update(ctx context.Context, id <span class="type">int</span>, input UpdateGoodsInput) (*model.Goods, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> goods model.Goods</span><br><span class="line">err := r.db.WithContext(ctx).Transaction(<span class="function"><span class="keyword">func</span><span class="params">(tx *gorm.DB)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := tx.First(&amp;goods, id).Error; 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">update := <span class="keyword">map</span>[<span class="type">string</span>]any&#123;&#125;</span><br><span class="line"><span class="keyword">if</span> input.Name != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">update[<span class="string">&quot;name&quot;</span>] = input.Name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> input.Status != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">update[<span class="string">&quot;status&quot;</span>] = input.Status</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(update) == <span class="number">0</span> &#123;</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 class="keyword">if</span> err := tx.Model(&amp;goods).Updates(update).Error; err != <span class="literal">nil</span> &#123;</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 class="keyword">return</span> tx.First(&amp;goods, id).Error</span><br><span class="line">&#125;)</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> errors.Is(err, gorm.ErrRecordNotFound) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, gorm.ErrRecordNotFound</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, fmt.Errorf(<span class="string">&quot;update user failed : %w&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;goods, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Update"><a href="#Update" class="headerlink" title="Update"></a>Update</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><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="params">(r *goodsRepository)</span></span> UpdateGoods(ctx context.Context, id <span class="type">int</span>, price <span class="type">int</span>) (*model.Goods, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> goods model.Goods</span><br><span class="line">err := r.db.WithContext(ctx).Transaction(<span class="function"><span class="keyword">func</span><span class="params">(tx *gorm.DB)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := tx.First(&amp;goods, id).Error; 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> err := tx.Model(&amp;goods).Update(<span class="string">&quot;price&quot;</span>, price).Error; 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> tx.First(&amp;goods, id).Error</span><br><span class="line">&#125;)</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> errors.Is(err, gorm.ErrRecordNotFound) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, gorm.ErrRecordNotFound</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, fmt.Errorf(<span class="string">&quot;update goods failed : %w&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;goods, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Delete"><a href="#Delete" class="headerlink" title="Delete"></a>Delete</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><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="params">(r *goodsRepository)</span></span> DeleteByID(ctx context.Context, id <span class="type">int64</span>) <span class="type">error</span> &#123;</span><br><span class="line">result := r.db.WithContext(ctx).Delete(&amp;model.Goods&#123;&#125;, id)</span><br><span class="line"><span class="keyword">if</span> result.Error != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;delete user failed : %w&quot;</span>, result.Error)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> result.RowsAffected == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> gorm.ErrRecordNotFound</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></pre></td></tr></table></figure><h4 id="Unscoped"><a href="#Unscoped" class="headerlink" title="Unscoped"></a>Unscoped</h4><p>物理删除：<code>Unscoped().Delete()</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="params">(r *goodsRepository)</span></span> unScopedDeleteByID(ctx context.Context, id <span class="type">int64</span>) <span class="type">error</span> &#123;</span><br><span class="line">result := r.db.WithContext(ctx).Unscoped().Delete(&amp;model.Goods&#123;&#125;, id)</span><br><span class="line"><span class="keyword">if</span> result.Error != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;hard delete user failed : %w&quot;</span>, result.Error)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> result.RowsAffected == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> gorm.ErrRecordNotFound</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></pre></td></tr></table></figure><h3 id="GORM-Hook"><a href="#GORM-Hook" class="headerlink" title="GORM Hook"></a>GORM Hook</h3><p><code>GORM Hook</code>是模型生命周期回调函数，会在创建、查询、更新、删除前后自动执行；Hook方法签名必须是<code>func(*grom.DB) error</code>。如果Hook返回error,GORM会停止后续操作并回滚当前事务。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *model.User)</span></span> BeforeCreate(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">    <span class="comment">// 创建用户后自动放行</span></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>通常场景为：</p><ul><li>创建前：校验字段、规范化email、生成UUID、设置默认状态</li><li>创建后：写审计字段、初始化用户配置</li><li>更新前：禁止修改敏感字段、校验状态流转</li><li>更新后：写变更日志、同步冗余字段</li><li>删除前：阻止删除系统管理员</li><li>删除后：写删除日志</li><li>查询后：设置默认展示字段、脱敏部分字段</li></ul><h4 id="Hook生命周期顺序"><a href="#Hook生命周期顺序" class="headerlink" title="Hook生命周期顺序"></a>Hook生命周期顺序</h4><h5 id="Create生命周期"><a href="#Create生命周期" class="headerlink" title="Create生命周期"></a>Create生命周期</h5><p>创建对象时，GORM的Hook顺序时：</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">BeforeSave</span><br><span class="line">BeforeCreate</span><br><span class="line">保存前置关联</span><br><span class="line">INSERT INTO database</span><br><span class="line">保存后之关联</span><br><span class="line">AfterCreate</span><br><span class="line">AfterSave</span><br><span class="line">commit或 rollback</span><br></pre></td></tr></table></figure><h5 id="Update生命周期"><a href="#Update生命周期" class="headerlink" title="Update生命周期"></a>Update生命周期</h5><p>更新对象时，GORM的Hook顺序是：</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">BeforeSave</span><br><span class="line">BeforeUpdate</span><br><span class="line">保存前置关联</span><br><span class="line">UPDATE database</span><br><span class="line">保存后置关联</span><br><span class="line">AfterUpdate</span><br><span class="line">AfterSave</span><br><span class="line">commit 或 rollback</span><br></pre></td></tr></table></figure><h5 id="Delete-生命周期"><a href="#Delete-生命周期" class="headerlink" title="Delete 生命周期"></a>Delete 生命周期</h5><p>删除对象时，Hook顺序是：</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">BeforeDelete</span><br><span class="line">DELETE FROM database</span><br><span class="line">AfterDelete</span><br><span class="line">commit 或 rollback</span><br></pre></td></tr></table></figure><p>如果模型里有<code>gorm.DeletedAt</code>字段，<code>GORM</code>默认执行软删除，也就是更新<code>deleted_at</code>字段，而不是物理删除记录。</p><h5 id="Query生命周期"><a href="#Query生命周期" class="headerlink" title="Query生命周期"></a>Query生命周期</h5><p>查询对象时，主要Hook是：</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">AfterFind</span><br></pre></td></tr></table></figure><p>就是数据从数据库加载出来后自动执行。</p><h4 id="Hook案例"><a href="#Hook案例" class="headerlink" title="Hook案例"></a>Hook案例</h4><p>在<code>internal/model/user_dto.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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// BeforeSave 会在`Create`和`Update`前执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> BeforeSave(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] BeforeSave&quot;</span>)</span><br><span class="line">u.Email = strings.ToLower(strings.TrimSpace(u.Email))</span><br><span class="line">u.Name = strings.TrimSpace(u.Name)</span><br><span class="line"><span class="keyword">if</span> u.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 required&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> u.Email == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;email is required&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="comment">// BeforeCreate 只在Create前执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> BeforeCreate(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook]  BeforeCreate&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> u.Status == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">u.Status = UserStatusActive</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> u.Password == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;password is required&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="comment">// AfterCreate 旨在Create后执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> AfterCreate(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] AfterCreate&quot;</span>)</span><br><span class="line">log := AuditLog&#123;</span><br><span class="line">Action:   <span class="string">&quot;CREATE&quot;</span>,</span><br><span class="line">Entity:   <span class="string">&quot;users&quot;</span>,</span><br><span class="line">EntityID: u.ID,</span><br><span class="line">Message:  fmt.Sprintf(<span class="string">&quot;created user :%s&quot;</span>, u.Name),</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err := tx.Create(&amp;log).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 写入日志报错时，会触发回滚</span></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="comment">// BeforeUpdate 只在update前执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> BeforeUpdate(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] BeforeUpdate&quot;</span>)</span><br><span class="line"><span class="comment">// ID 1为超级管理员，不允许修改密码</span></span><br><span class="line"><span class="keyword">if</span> u.ID == <span class="number">1</span> &amp;&amp; tx.Statement.Changed(<span class="string">&quot;Password&quot;</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;superadmin password cannot be changed&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> tx.Statement.Changed(<span class="string">&quot;Status&quot;</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> u.Status != UserStatusDisable &amp;&amp; u.Status != UserStatusActive &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;invalid user status&quot;</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">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AfterUpdate 只在update后执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *User)</span></span> AfterUpdate(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] AfterUpdate&quot;</span>)</span><br><span class="line">log := AuditLog&#123;</span><br><span class="line">Action:   <span class="string">&quot;UPDATE&quot;</span>,</span><br><span class="line">Entity:   <span class="string">&quot;users&quot;</span>,</span><br><span class="line">EntityID: u.ID,</span><br><span class="line">Message:  fmt.Sprintf(<span class="string">&quot;updated user : %s&quot;</span>, u.Name),</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err := tx.Create(&amp;log).Error; 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="params">(u *User)</span></span> BeforeDelete(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] BeforeDelete&quot;</span>)</span><br><span class="line"><span class="comment">// 本案例，ID为1是超级管理员，不能删除自己</span></span><br><span class="line"><span class="keyword">if</span> u.ID == <span class="number">1</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;superadmin cannot be deleted&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="params">(u *User)</span></span> AfterDelete(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] AfterDelete&quot;</span>)</span><br><span class="line"></span><br><span class="line">log := AuditLog&#123;</span><br><span class="line">Action:   <span class="string">&quot;DELETE&quot;</span>,</span><br><span class="line">Entity:   <span class="string">&quot;users&quot;</span>,</span><br><span class="line">EntityID: u.ID,</span><br><span class="line">Message:  fmt.Sprintf(<span class="string">&quot;deleted user : %s&quot;</span>, u.Name),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := tx.Create(&amp;log).Error; 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><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="params">(u *User)</span></span> AfterFind(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;[Hook] AfterFind&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> u.Email == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">u.Email = u.Name + <span class="string">&quot;@dev.net.cn&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></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">[30.607ms] [rows:1] INSERT INTO `<span class="built_in">users</span>` (`name`,`email`,`age`,`password`,`status`,`create_at`,`update_at`) VALUES (<span class="string">&#x27;devid&#x27;</span>,<span class="string">&#x27;devid@qq.com&#x27;</span>,19,<span class="string">&#x27;123456&#x27;</span>,<span class="string">&#x27;active&#x27;</span>,<span class="string">&#x27;2026-06-17 16:27:22.092&#x27;</span>,<span class="string">&#x27;2026-06-17 16:27:22.092&#x27;</span>)</span><br><span class="line">[GIN] 2026/06/17 - 16:27:22 | 400 |  31.75ms |       127.0.0.1 | POST     <span class="string">&quot;/api/d1/users&quot;</span></span><br><span class="line">2026/06/17 16:27:49 service ....</span><br><span class="line">[Hook] BeforeSave</span><br><span class="line">[Hook]  BeforeCreate</span><br><span class="line">[Hook] AfterCreate</span><br><span class="line">[GIN] 2026/06/17 - 16:27:49 | 200 |  30.19ms |       127.0.0.1 | POST     <span class="string">&quot;/api/d1/users&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><span class="line">2</span><br><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">[GIN] 2026/06/17 - 16:27:49 | 200 |  30.19ms |       127.0.0.1 | POST     <span class="string">&quot;/api/d1/users&quot;</span></span><br><span class="line">[Hook] AfterFind</span><br><span class="line">[Hook] AfterFind</span><br><span class="line">[Hook] AfterFind</span><br><span class="line">[Hook] AfterFind</span><br><span class="line">[GIN] 2026/06/17 - 16:31:12 | 200 |   9.81ms |       127.0.0.1 | GET      <span class="string">&quot;/api/d1/users?page=1&amp;page_size=15&quot;</span></span><br></pre></td></tr></table></figure><p>每查出一条数据，就会调用一次<code>AfterFind</code>。</p><div class="note info simple"><p><code>BeforeSave</code>和<code>BeforeCreate</code>的区别是：<code>BeforeSave</code>在创建和更新前都会执行；<code>BeforeCreate</code>只在创建前执行。如果逻辑只适用于新增、应该放在<code>BeforeCreate</code>，如果创建和更新都需要，例如：<code>trim</code>去掉空字符等，可以放在<code>BeforeSave</code>。</p></div><p>根据案例还发现，所有的参数都是<code>tx *grom.DB</code>，原因就是</p><div class="note info simple"><p><code>tx</code>表示当前<code>GORM</code>操作所在的事务上下文。Hook里使用<code>tx</code>可以保证著操作和<code>Hook</code>里的副操作在同一事务内，要么都成功、要么都回滚。如果使用<code>db</code>，可能会导致数据不一致。</p></div><p>对于<code>Updates</code>和<code>Update</code>，还需要监控字段值是否发生变化，可以使用<code>tx.Statment.Changed(&quot;FieldName&quot;)</code>判断字段是否发生变化。</p><h4 id="SetColumn"><a href="#SetColumn" class="headerlink" title="SetColumn"></a>SetColumn</h4><p>如果模型中，存在<code>updated_at</code>字段，在执行<code>Updates</code>或者<code>Update</code>时，更新这个<code>updated_at</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="params">(u *User)</span></span> BeforeUpdate(tx *gorm.DB) <span class="type">error</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> tx.Statement.Changed(<span class="string">&quot;Status&quot;</span>)&#123;</span><br><span class="line">        tx.Statment.SetColumn(<span class="string">&quot;UpdateAt&quot;</span>,time.Now())</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></pre></td></tr></table></figure><p>对于这个字段的更新，也是推荐使用这种方式，能够确保该字段进入更新语句。</p><h4 id="跳过Hook"><a href="#跳过Hook" class="headerlink" title="跳过Hook"></a>跳过Hook</h4><p>如果不想触发<code>Hook</code>，可以通过<code>Session(&amp;gorm.Session(SkipHooks:true))</code>跳过<code>Hook</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">err := db.Session(&amp;gorm.Session&#123;</span><br><span class="line">    SkipHook: <span class="literal">true</span>,</span><br><span class="line">&#125;).Create(&amp;user).Error</span><br></pre></td></tr></table></figure><h3 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h3><h4 id="修改Model"><a href="#修改Model" class="headerlink" title="修改Model"></a>修改Model</h4><p><code>model/user_dto.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></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">`gorm:&quot;primaryKey&quot;`</span></span><br><span class="line">Name     <span class="type">string</span> <span class="string">`gorm:&quot;size:50;not null&quot;`</span></span><br><span class="line">Email    <span class="type">string</span> <span class="string">`gorm:&quot;size:100;not null&quot;`</span></span><br><span class="line">Age      <span class="type">int</span>    <span class="string">`gorm:&quot;not null&quot;`</span></span><br><span class="line">Password <span class="type">string</span> <span class="string">`gorm:&quot;size:255;not null&quot;`</span></span><br><span class="line">Status   <span class="type">string</span> <span class="string">`gorm:&quot;size:20;not null&quot;`</span></span><br><span class="line">CreateAt time.Time</span><br><span class="line">UpdateAt time.Time</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="修改repository"><a href="#修改repository" class="headerlink" title="修改repository"></a>修改repository</h4><p><code>internal/repository/user_repository</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实际用下来，应该新建一个user_gorm_resp.go好一点</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewUserRepository</span><span class="params">(db *sql.DB)</span></span> *UserRepository &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserRepository&#123;</span><br><span class="line">users: <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">int</span>]*model.User),</span><br><span class="line">db:    db,</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> GormUserRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">db *gorm.DB</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">NewGormUserRepository</span><span class="params">(db *gorm.DB)</span></span> *GormUserRepository &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;GormUserRepository&#123;db: db&#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">(r *GormUserRepository)</span></span> Create(ctx context.Context, user *model.User) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> err := r.db.WithContext(ctx).Create(user).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</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="function"><span class="keyword">func</span> <span class="params">(r *GormUserRepository)</span></span> FindByID(ctx context.Context, id <span class="type">int</span>) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> user model.User</span><br><span class="line"></span><br><span class="line">err := r.db.WithContext(ctx).First(&amp;user, id).Error</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> errors.Is(err, gorm.ErrRecordNotFound) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, errs.ErrUserNotFound</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;user, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="修改service-1"><a href="#修改service-1" class="headerlink" title="修改service"></a>修改service</h4><p><code>internal/service/user_service.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UserService <span class="keyword">struct</span> &#123;</span><br><span class="line">userRepo     *repository.UserRepository</span><br><span class="line">userGormRepo *repository.GormUserRepository</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">(userRepo *repository.UserRepository, gormRepo *repository.GormUserRepository)</span></span> *UserService &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserService&#123;</span><br><span class="line">userRepo:     userRepo,</span><br><span class="line">userGormRepo: gormRepo,</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> FindUserByID(ctx context.Context, id <span class="type">int</span>) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">//user, err := s.userRepo.FindByID(ctx, id)</span></span><br><span class="line">user, err := s.userGormRepo.FindByID(ctx, id)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">&quot;查询用户失败: %v&quot;</span>, err)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</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></pre></td></tr></table></figure><h4 id="修改db-go"><a href="#修改db-go" class="headerlink" title="修改db.go"></a>修改db.go</h4><p><code>pkg/db/db.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="comment">// 新增</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewGormDB</span><span class="params">(dsn <span class="type">string</span>)</span></span> (*gorm.DB, <span class="type">error</span>) &#123;</span><br><span class="line">db, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config&#123;&#125;)</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := db.AutoMigrate(&amp;model.User&#123;&#125;); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> db, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="修改router-go-1"><a href="#修改router-go-1" class="headerlink" title="修改router.go"></a>修改router.go</h4><p><code>routers/router.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></pre></td><td class="code"><pre><span class="line">userGormRepo := repository.NewGormUserRepository(gorm)</span><br><span class="line">userService := service.NewUserService(userRepo, userGormRepo)</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="function"><span class="keyword">func</span> <span class="title">SetupRouter</span><span class="params">(db *sql.DB, gorm *gorm.DB)</span></span> *gin.Engine</span><br></pre></td></tr></table></figure><h4 id="修改main-go-1"><a href="#修改main-go-1" class="headerlink" title="修改main.go"></a>修改main.go</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">gormInstance, err := db.NewGormDB(dsn)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalf(<span class="string">&quot;Gorm数据库连接失败: %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">r := routers.SetupRouter(dbInstance, gormInstance)</span><br></pre></td></tr></table></figure><p>使用<code>GORM</code>可以明显感觉到开发效率高很多，不需要自己去写<code>SQL</code>，然后做映射，像<code>Hibernate</code>那样，<code>CRUD很快</code>、<code>自动迁移方便</code>、<code>关联查询方便</code>、<code>Hook / Transaction / Preload功能完整</code>。</p><p>当然有优点，那就有缺点：</p><ul><li>复杂SQL可读性差</li><li>需要理解ORM生成的SQL</li><li>很难做到性能调优</li></ul><p>选择<code>database/sql</code>的理由：</p><ul><li>需要自己掌控SQL</li><li>需要性能调优</li><li>需要复杂查询</li></ul><p>除此之外，<code>GORM</code>开发效率更高。</p><h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><p>谈到<code>DB</code>，就绕不开事务。</p><h3 id="database-sql事务"><a href="#database-sql事务" class="headerlink" title="database&#x2F;sql事务"></a>database&#x2F;sql事务</h3><p>在Go语言中，事物的生命周期由<code>sql.Tx</code>对象管理。一个标准得事务包含以下四个阶段：</p><ol><li>开启事务：调用<code>db.BeginTx(ctx,opts)</code>。此时，底层连接池会独占一格固定的TCP连接，并向MySQL发送<code>BEGIN</code>指令。</li><li>执行业务：后续所有的SQL语句（CRUD）必须调用<code>tx.ExecContext</code>或者<code>tx.QueryContext</code>，而不是<code>db.ExceContext</code>。</li><li>异常回滚：如果中途任何一步报错，或者程序发生<code>Panic</code>，必须执行<code>tx.Rollback()</code></li><li>成功提交： 所有步骤完美通过后，执行<code>tx.Commit()</code>，底层连接释放，重归连接池。</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> repository</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;context&quot;</span></span><br><span class="line"><span class="string">&quot;database/sql&quot;</span></span><br><span class="line"><span class="string">&quot;errors&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> AccountRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">db *sql.DB</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">(r *AccountRepository)</span></span> Transfer(ctx context.Context, fromID, toID <span class="type">int</span>, amount <span class="type">float64</span>) (err <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">// 开启事务（可以传入Isolation等级配置，默认使用的是Read Commited / Repeatable Read）</span></span><br><span class="line">tx, err := r.db.BeginTx(ctx, &amp;sql.TxOptions&#123;Isolation: sql.LevelDefault&#125;)</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><br><span class="line"><span class="comment">// 利用defer确保事务绝对不会死锁</span></span><br><span class="line"><span class="comment">// 如果程序中图Panic奔溃了，或者函数因报错提前return,defer会自动触发Rollback</span></span><br><span class="line"><span class="comment">// 如果后面成功执行了 tx.Commit()，tx.Rollback() 会安全得返回sql.ErrTxDone错误，底层会自动忽略。</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> p := <span class="built_in">recover</span>(); p != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 发生Panic,强制回滚</span></span><br><span class="line">_ = tx.Rollback()</span><br><span class="line"><span class="comment">// 继续排除Panic 让上层拦截</span></span><br><span class="line"><span class="built_in">panic</span>(p)</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"><span class="comment">// 发送业务错误，回滚事务</span></span><br><span class="line">_ = tx.Rollback()</span><br><span class="line">log.Printf(<span class="string">&quot;事务回滚成功，原因：%v&quot;</span>, 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="comment">// 必须使用 tx.ExceContext,绝对不能用 r.db.ExecContext,否则就不是在同一个事务里了</span></span><br><span class="line"><span class="comment">// 扣钱</span></span><br><span class="line">query1 := <span class="string">`UPDATE accounts SET balance=balance - ? WHERE id = ? AND balance &gt;= ?`</span></span><br><span class="line">res1, err := tx.ExecContext(ctx, query1, amount, fromID, amount)</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">// err被赋值，出发defer中的 Rollback</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">rowsAffected1, _ := res1.RowsAffected()</span><br><span class="line"><span class="keyword">if</span> rowsAffected1 == <span class="number">0</span> &#123;</span><br><span class="line">err = errors.New(<span class="string">&quot;扣款失败，余额不足或账户不存在&quot;</span>)</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 执行加钱</span></span><br><span class="line">query2 := <span class="string">`UPDATE accounts SET balance=balance + ? WHERE id = ?`</span></span><br><span class="line">res2, err := tx.ExecContext(ctx, query2, amount, toID)</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">rowsAffected2, _ := res2.RowsAffected()</span><br><span class="line"><span class="keyword">if</span> rowsAffected2 == <span class="number">0</span> &#123;</span><br><span class="line">err = errors.New(<span class="string">&quot;收款账号不存在&quot;</span>)</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 所有SQL都成功的话，他显示提交事务</span></span><br><span class="line"><span class="comment">// 只有这一步成功，MySQL才会真正持久化数据</span></span><br><span class="line"><span class="keyword">if</span> err = tx.Commit(); 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></pre></td></tr></table></figure><p>对于事务，需要特别注意如下三个问题：</p><p>避免混用<code>db</code>和<code>tx</code>:</p><div class="note info simple"><p>开启事务后，中间得更新语句如果写成<code>r.db.ExecContext()</code>，那么就会导致<code>r.db</code>从数据库连接池再去获取一个新的连接，这条语句就在事务之外独立运行。而<code>tx</code>连接依然在等待。不仅破坏了原子性，还极其容易导致<code>数据库死锁（Deadlock）</code>，因为两个TCP连接可能在互相等待对方释放同一行数据得锁。</p></div><p>忘记写<code>defer tx.Rollback()</code>导致连接池枯竭：</p><div class="note info simple"><p>如果执行中途报错，你没有写<code>defer</code>，也没有在<code>if err != nil</code>里手动调用<code>Rollback()</code>，那么这个事务在<code>MySQL</code>里保持<code>PENDING</code>状态，它占用得TCP连接永远不会释放。高并发下，几秒钟就能把项目的数据库连接池（<code>SetMaxOpenConns</code>）全部耗尽，导致整个系统瘫痪。</p></div><p>在事务里操作其他事情</p><div class="note info simple"><p>事务开启后，MySQL会对相关的行加排他锁(X锁)。如果中间请求了第三方网络接口，就意味着请求的时间就加到MySQL的锁的时间。所以，事务内只做纯粹的数据库增删改查，其他的操作必须在开启事务之前。或者提交事务之后。</p></div><h3 id="GORM事务"><a href="#GORM事务" class="headerlink" title="GORM事务"></a>GORM事务</h3><p><code>GORM</code>提供了两种事务处理机制：<strong>自动事务（闭包机制：推荐）</strong> 和<code>手动事务</code>。可以类比于Spring框架中的<code>@Transcational</code>注解。 </p><h4 id="自动事务"><a href="#自动事务" class="headerlink" title="自动事务"></a>自动事务</h4><p>自动事务的核心规则：</p><ul><li>如果闭包函数返回了<code>nil</code>，GORM会自动提交事务。</li><li>如果闭包函数返回了任何<code>error</code>，或者内部发生了<code>Panic</code>，GORM会自动回滚事务，并把对应的错误或者Panic往外抛。</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="keyword">type</span> Profile <span class="keyword">struct</span> &#123;</span><br><span class="line">UserID <span class="type">int</span> <span class="string">`gorm:&quot;uniqueIndex&quot;`</span></span><br><span class="line">Hobby  <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">(r *GormUserRepository)</span></span> RegisterUser(ctx context.Context, name, hobby <span class="type">string</span>) <span class="type">error</span> &#123;</span><br><span class="line"><span class="comment">// 开启事务</span></span><br><span class="line"><span class="comment">// 在闭包内部，所有数据库操作必须使用参数传递进来的tx，不能使用 r.db</span></span><br><span class="line">err := r.db.WithContext(ctx).Transaction(<span class="function"><span class="keyword">func</span><span class="params">(tx *gorm.DB)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="comment">// 创建用户</span></span><br><span class="line">user := model.User&#123;Name: name, Status: model.UserStatusActive&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := tx.Create(&amp;user).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 返回错误，GORM自动回滚</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 模拟业务不合规</span></span><br><span class="line"><span class="keyword">if</span> name == <span class="string">&quot;hack&quot;</span> &#123;</span><br><span class="line"><span class="comment">// 自动回滚</span></span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;违规用户名，拒绝注册&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 业务步骤2</span></span><br><span class="line">profile := Profile&#123;UserID: user.ID, Hobby: hobby&#125;</span><br><span class="line"><span class="keyword">if</span> err := tx.Create(&amp;profile).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 返回错误，自动回滚</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 返回nil，GORM底层挥拳自动执行commit</span></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 class="comment">// 闭包完成后，外界能拿到最终的错误状态</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="手动事务"><a href="#手动事务" class="headerlink" title="手动事务"></a>手动事务</h4><p>适合精细化控制的方式，如果不想在闭包、或者事务链路非常长、需要根据复杂的跨系统业务接过来人为决定何时提交，GORM也保留了类似原生<code>database/sql</code>的手动事务机制。</p><p>手动事务的核心步骤：</p><ol><li>开启：<code>tx := db.Begin()</code></li><li>回滚：<code>tx.Rollback()</code></li><li>提交：<code>tx.Commit()</code></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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *GormUserRepository)</span></span> RegisterUserManual(ctx context.Context, name <span class="type">string</span>) (err <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">// 开启事务</span></span><br><span class="line">tx := r.db.WithContext(ctx).Begin()</span><br><span class="line"><span class="keyword">if</span> tx.Error != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> tx.Error</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// defer 拦截panic和Error</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> p := <span class="built_in">recover</span>(); p != <span class="literal">nil</span> &#123;</span><br><span class="line">tx.Rollback()</span><br><span class="line"><span class="built_in">panic</span>(p)</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">tx.Rollback()</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line"><span class="comment">// 执行业务</span></span><br><span class="line">user := model.User&#123;Name: name&#125;</span><br><span class="line"><span class="keyword">if</span> err = tx.Create(&amp;user).Error; 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="comment">// 提交事务</span></span><br><span class="line"><span class="keyword">if</span> err = tx.Commit().Error; 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></pre></td></tr></table></figure><h4 id="全局禁用事务"><a href="#全局禁用事务" class="headerlink" title="全局禁用事务"></a>全局禁用事务</h4><p><code>GORM</code>为了保证单条增删改语句的数据安全，默认情况下哪怕只调用一次<code>db.Create(&amp;user)</code>，它也会在底层隐式的开启<code>BEGIN</code>和<code>COMMIT</code>。</p><ul><li>如果追求极致的并发性能，可以通过在初始化数据库时，配置<code>SkipDefaultTransaction</code>来关掉这个隐式特性。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewGormDB</span><span class="params">(dsn <span class="type">string</span>)</span></span> (*gorm.DB, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">//db, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config&#123;&#125;)</span></span><br><span class="line">db, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config&#123;</span><br><span class="line">SkipDefaultTransaction: <span class="literal">true</span>,</span><br><span class="line">&#125;)</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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := db.AutoMigrate(&amp;model.User&#123;&#125;); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> db, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="事务传递"><a href="#事务传递" class="headerlink" title="事务传递"></a>事务传递</h4><p>我查了下<code>GORM</code>不支持像<code>Spring</code>那样的事务传递（<code>@Transactional(propagation=Propagation.REQUIRED)</code>），但也可以依靠<code>*gorm.DB</code>上下文对象的显示传递和<code>Transaction</code>闭包机制，可以实现媲美于Spring的事务传播效果。</p><p>下面模拟一个<code>PROPAGATION_REQUIRED</code></p><p><code>internal/service/user_service.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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *UserService)</span></span> UpdateUser(ctx context.Context, u model.User) (err <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">// 开启外部事务，记得讲userGormRepo.Db大写，导出。</span></span><br><span class="line"><span class="keyword">return</span> s.userGormRepo.Db.Transaction(<span class="function"><span class="keyword">func</span><span class="params">(tx *gorm.DB)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="comment">// 调用更新，讲tx显示传递过去</span></span><br><span class="line"><span class="keyword">if</span> err := s.userGormRepo.UpdateInfo(ctx, tx, &amp;u); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 外层回滚</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 调用更新，讲tx显示传递过去（模拟嵌套事务）</span></span><br><span class="line"><span class="keyword">if</span> err := s.userGormRepo.Rename(ctx, tx, <span class="string">&quot;Jerry&quot;</span>, <span class="number">1</span>); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 子事务报错，外层也会一起回滚</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></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">&#125;</span><br></pre></td></tr></table></figure><p><code>internal/repository/user_repository.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="function"><span class="keyword">func</span> <span class="params">(r *GormUserRepository)</span></span> UpdateInfo(ctx context.Context, tx *gorm.DB, user *model.User) <span class="type">error</span> &#123;</span><br><span class="line"><span class="comment">// 这里必须再次调用tx.Transaction() ，并且建议加上WithContext(ctx)</span></span><br><span class="line"><span class="comment">// 如果外部传入的tx已经是一个十五，GORM底层不会发送BEGIN，而是发送SAVEPOINT gorm_sp_1</span></span><br><span class="line"><span class="keyword">return</span> tx.WithContext(ctx).Transaction(<span class="function"><span class="keyword">func</span><span class="params">(nestedTX *gorm.DB)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">updateSql := <span class="string">&quot;update users set name=? where id=?&quot;</span></span><br><span class="line"><span class="keyword">if</span> err := nestedTX.Exec(updateSql, user.Name, user.ID).Error; err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 嵌套子十五失败，自动回滚到SAVEPOINT</span></span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 子事务成功，释放当前SAVEPOINT</span></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">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *GormUserRepository)</span></span> Rename(ctx context.Context, tx *gorm.DB, name <span class="type">string</span>, id <span class="type">int</span>) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">return</span> tx.WithContext(ctx).Transaction(<span class="function"><span class="keyword">func</span><span class="params">(nestedTx *gorm.DB)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">updateSql := <span class="string">&quot;update users set name=? where id=?&quot;</span></span><br><span class="line"><span class="keyword">if</span> err := nestedTx.Exec(updateSql, name, id).Error; 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">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-go-web-05/</id>
    <link href="https://dev.net.cn/learning-go-web-05/"/>
    <published>2026-06-05T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天开始学习数据库操作：</p>
<ul>
<li>database&#x2F;sql：标准库，偏底层，显示SQL</li>
<li>GORM： ORM 开发效率高，适合快速CRUD</li>
</ul>]]>
    </summary>
    <title>Go Web学习笔记（五）- 数据库ORM</title>
    <updated>2026-06-05T02: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>今天开始学习Gin的项目分层，对于Java程序员来说，这很轻松，但是Go与Java多少有些差异。不过大体上都是通用的。</p><span id="more"></span><p>我将<code>goweb</code>项目的目录做了下归类。将<code>main()</code>里面的路由提取到专门管理路由的包里面。还有业务逻辑、错误处理、DTO等等。目前的项目结构如下：</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">goweb/</span><br><span class="line">├── <span class="keyword">go</span>.mod</span><br><span class="line">├── main.<span class="keyword">go</span></span><br><span class="line">├── errs/                <span class="comment">// 业务错误</span></span><br><span class="line">│   └── <span class="type">error</span>.<span class="keyword">go</span> </span><br><span class="line">├── middleware/         <span class="comment">// 中间件</span></span><br><span class="line">│   └── handlers.<span class="keyword">go</span>  </span><br><span class="line">├── model/                </span><br><span class="line">│   └── user_dto.<span class="keyword">go</span>     <span class="comment">// 存放所有用户相关的输入输出结构体</span></span><br><span class="line">├── handler/         <span class="comment">// 处理HTTP参数和响应 相当于controller，handler是Go风格，controller是java风格，用哪个都行</span></span><br><span class="line">│   └── user_handler.<span class="keyword">go</span></span><br><span class="line">├── services/            <span class="comment">// 处理业务逻辑</span></span><br><span class="line">    └── user_service.<span class="keyword">go</span></span><br><span class="line">└── respository/            <span class="comment">// 处理数据访问</span></span><br><span class="line">    └── user_resppsitory.<span class="keyword">go</span></span><br></pre></td></tr></table></figure><p>对于分层，必要性和<code>Java</code>一样，避免所有的逻辑都写在同一个文件里，少了复用性和整洁。不管什么语言，一般也都会分成<code>handler</code> -&gt; <code>service</code> -&gt; <code>respository</code>。</p><h2 id="model层"><a href="#model层" class="headerlink" title="model层"></a>model层</h2><p>也就是存放一些DTO的目录。<code>model/user_dto.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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> model</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;time&quot;</span></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">ID       <span class="type">int</span></span><br><span class="line">Name     <span class="type">string</span></span><br><span class="line">Email    <span class="type">string</span></span><br><span class="line">Age      <span class="type">int</span></span><br><span class="line">Password <span class="type">string</span></span><br><span class="line">Status   <span class="type">string</span></span><br><span class="line">CreateAt time.Time</span><br><span class="line">UpdateAt time.Time</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CreateUserReq <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; binding:&quot;required,min=2,max=5&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">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=6,max=20&quot;`</span></span><br><span class="line">Address  <span class="type">string</span> <span class="string">`json:&quot;address&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><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">Age      <span class="type">int</span>    <span class="string">`json:&quot;age&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">Password <span class="type">string</span> <span class="string">`json:&quot;password,omitempty&quot;`</span></span><br><span class="line">Role     <span class="type">string</span> <span class="string">`json:&quot;role&quot; binding:&quot;required,oneof=admin user&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">UserStatusActive  = <span class="string">&quot;active&quot;</span></span><br><span class="line">UserStatusDisable = <span class="string">&quot;disabled&quot;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>Model</code>、<code>RequestDTO</code>、<code>ResponseDTO</code>建议分开：<code>Model</code>表示内部数据结构，<code>RequestDTO</code>表示入参，<code>ResponseDTO</code>表示出参。这样避免将敏感字段返回给前端，也能避免数据库结构变化影响API。</p></div><h2 id="reponsitory层"><a href="#reponsitory层" class="headerlink" title="reponsitory层"></a>reponsitory层</h2><p>这一层还没有学习，实际上就是数据访问层，对应Spring的<code>dao</code>&#x2F;<code>mapper</code>。先模拟一下：</p><p><code>repository/user_repository.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> respository</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;sync&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/model&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">mu     sync.RWMutex</span><br><span class="line">nextID <span class="type">int</span></span><br><span class="line">users  <span class="keyword">map</span>[<span class="type">int</span>]*model.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="title">NewUserRepository</span><span class="params">()</span></span> *UserRepository &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserRepository&#123;</span><br><span class="line">nextID: <span class="number">1</span>,</span><br><span class="line">users:  <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">int</span>]*model.User),</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">(r *UserRepository)</span></span> Create(user *model.User) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">r.mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> r.mu.Unlock()</span><br><span class="line"></span><br><span class="line">user.ID = r.nextID</span><br><span class="line">r.nextID++</span><br><span class="line"></span><br><span class="line">r.users[user.ID] = user</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></pre></td></tr></table></figure><h2 id="service层"><a href="#service层" class="headerlink" title="service层"></a>service层</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> service</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;context&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/model&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/repository&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserService <span class="keyword">struct</span> &#123;</span><br><span class="line">userRepo *repository.UserRepository</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">(userRepo *repository.UserRepository)</span></span> *UserService &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserService&#123;</span><br><span class="line">userRepo: userRepo,</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> CreateUserInput <span class="keyword">struct</span> &#123;</span><br><span class="line">Name     <span class="type">string</span></span><br><span class="line">Email    <span class="type">string</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="function"><span class="keyword">func</span> <span class="params">(s *UserService)</span></span> CreateUser(ctx context.Context, input CreateUserInput) (*model.User, <span class="type">error</span>) &#123;</span><br><span class="line">user := &amp;model.User&#123;</span><br><span class="line">Name:     input.Name,</span><br><span class="line">Email:    input.Email,</span><br><span class="line">Password: input.Password,</span><br><span class="line">Status:   model.UserStatusActive,</span><br><span class="line">CreateAt: time.Now(),</span><br><span class="line">UpdateAt: time.Now(),</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//调用repository</span></span><br><span class="line"><span class="keyword">return</span> s.userRepo.Create(user)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>service</code>方法传递<code>context.Context</code>的原因是<code>context</code>用于传递<code>请求生命周期</code>、<code>超时</code>和<code>取消信号</code>。数据库查询、RPC调用、外部API调用都应该支持<code>context</code>，这样请求取消时下游操作也能及时停止。</p></div><h2 id="handler层"><a href="#handler层" class="headerlink" title="handler层"></a>handler层</h2><p><code>handler/user_handler.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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> handler</span><br><span class="line"></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;strings&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/errs&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/model&quot;</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;dev.net.cn/goweb/service&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="keyword">type</span> UserHandler <span class="keyword">struct</span> &#123;</span><br><span class="line">userService *service.UserService</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">NewUserHandler</span><span class="params">(userService *service.UserService)</span></span> *UserHandler &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;UserHandler&#123;</span><br><span class="line">userService: userService,</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">GetUserListHandler</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">page, err := strconv.Atoi(c.DefaultQuery(<span class="string">&quot;page&quot;</span>, <span class="string">&quot;1&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> 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">pageSize, err := strconv.Atoi(c.DefaultQuery(<span class="string">&quot;page_size&quot;</span>, <span class="string">&quot;10&quot;</span>))</span><br><span class="line"><span class="keyword">if</span> 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">response.PageOK(c, []gin.H&#123;</span><br><span class="line">&#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;Tom&quot;</span>, <span class="string">&quot;age&quot;</span>: <span class="number">18</span>&#125;,</span><br><span class="line">&#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;Jerry&quot;</span>, <span class="string">&quot;age&quot;</span>: <span class="number">16</span>&#125;,</span><br><span class="line">&#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;Tony&quot;</span>, <span class="string">&quot;age&quot;</span>: <span class="number">28</span>&#125;,</span><br><span class="line">&#125;, page, pageSize)</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">GetUserInfoByIdHandler</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">response.BadRequest(c, <span class="string">&quot;id is null&quot;</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">response.OK(c, 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"><span class="string">&quot;age&quot;</span>:  <span class="number">18</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="params">(h *UserHandler)</span></span> CreateUserHandler(c *gin.Context) &#123;</span><br><span class="line"><span class="keyword">var</span> user model.CreateUserReq</span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;user); 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 class="keyword">if</span> err := validateEmail(user.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">user1, err := h.userService.CreateUser(c.Request.Context(), service.CreateUserInput&#123;</span><br><span class="line">Name:     user.Name,</span><br><span class="line">Age:      user.Age,</span><br><span class="line">Email:    user.Email,</span><br><span class="line">Password: user.Password,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> 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">res := model.UserResponse&#123;</span><br><span class="line">ID:    <span class="number">1</span>,</span><br><span class="line">Name:  user1.Name,</span><br><span class="line">Age:   user1.Age,</span><br><span class="line">Email: user1.Email,</span><br><span class="line">&#125;</span><br><span class="line">response.OK(c, res)</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">UpdateUserHandler</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">response.BadRequest(c, <span class="string">&quot;id is null&quot;</span>)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 模拟根据ID获取用户</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> user model.CreateUserReq</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := c.ShouldBindJSON(&amp;user); err != <span class="literal">nil</span> &#123;</span><br><span class="line">response.BadRequest(c, err.Error())</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">res := model.UserResponse&#123;</span><br><span class="line">ID:    <span class="number">1</span>,</span><br><span class="line">Name:  user.Name,</span><br><span class="line">Age:   user.Age,</span><br><span class="line">Email: user.Email,</span><br><span class="line">&#125;</span><br><span class="line">response.OK(c, res)</span><br><span class="line"></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">DeleteUserByIdHandler</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">response.BadRequest(c, <span class="string">&quot;id is null&quot;</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">response.OK(c, <span class="string">&quot;用户已被删除&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// FormLogin /*</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">FormLogin</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">username := c.PostForm(<span class="string">&quot;username&quot;</span>)</span><br><span class="line">password := c.PostForm(<span class="string">&quot;password&quot;</span>)</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;username&quot;</span>: username,</span><br><span class="line"><span class="string">&quot;password&quot;</span>: password,</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">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">if</span> strings.HasSuffix(email, <span class="string">&quot;@qq.com&quot;</span>) &#123;</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 class="keyword">return</span> errs.ErrEmailExists</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里只做了一个模拟例子：<code>func (h *UserHandler) CreateUserHandler(c *gin.Context)</code>。</p><p>此时就完成了<code>handler</code> -&gt; <code>service</code> -&gt; <code>repository</code>的模拟。</p><div class="note info simple"><p><code>Handler</code>和<code>Service</code>的边界：Handler处理协议层的问题，例如<code>JSON Body</code>、路径参数、相应格式；<code>Service</code>处理业务规则，例如创建用户、校验角色、判断权限等。<code>Service</code>不应该依赖<code>Gin</code></p></div><h2 id="routers层"><a href="#routers层" class="headerlink" title="routers层"></a>routers层</h2><p>这里统一管理路由：<code>routers/route.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><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="keyword">package</span> routers</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/handler&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/middleware&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/repository&quot;</span></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/service&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">SetupRouter</span><span class="params">()</span></span> *gin.Engine &#123;</span><br><span class="line"></span><br><span class="line">r := gin.New()</span><br><span class="line">r.Use(gin.Recovery())</span><br><span class="line"><span class="comment">//r.Use(LoggerMiddleware())</span></span><br><span class="line">r.Use(gin.Logger())</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建</span></span><br><span class="line">userRepo := repository.NewUserRepository()</span><br><span class="line">userService := service.NewUserService(userRepo)</span><br><span class="line">userHandler := handler.NewUserHandler(userService)</span><br><span class="line"></span><br><span class="line"><span class="comment">//  RESTful API</span></span><br><span class="line">d1Group := r.Group(<span class="string">&quot;/api/d1/&quot;</span>)</span><br><span class="line">&#123;</span><br><span class="line">d1Group.GET(<span class="string">&quot;/users&quot;</span>, handler.GetUserListHandler)</span><br><span class="line">d1Group.GET(<span class="string">&quot;/users/:id&quot;</span>, handler.GetUserInfoByIdHandler)</span><br><span class="line">d1Group.POST(<span class="string">&quot;/users&quot;</span>, userHandler.CreateUserHandler)</span><br><span class="line">d1Group.PUT(<span class="string">&quot;/users/:id&quot;</span>, handler.UpdateUserHandler)</span><br><span class="line">d1Group.DELETE(<span class="string">&quot;users/:id&quot;</span>, handler.DeleteUserByIdHandler)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问该链路需要token认证</span></span><br><span class="line">d2Group := r.Group(<span class="string">&quot;/api/d2&quot;</span>).Use(middleware.AuthMiddleware())</span><br><span class="line">&#123;</span><br><span class="line">d2Group.GET(<span class="string">&quot;/auth&quot;</span>, handler.GetUserListHandler)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> r</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><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">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 创建一个新的Gin路由引擎（不带任何中间件）</span></span><br><span class="line">r := routers.SetupRouter()</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></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-go-web-04/</id>
    <link href="https://dev.net.cn/learning-go-web-04/"/>
    <published>2026-06-04T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天开始学习Gin的项目分层，对于Java程序员来说，这很轻松，但是Go与Java多少有些差异。不过大体上都是通用的。</p>]]>
    </summary>
    <title>Go Web学习笔记（四）- 项目分层</title>
    <updated>2026-06-04T02: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>今天学习的知识点：</p><ul><li>Gin middleware</li><li>c.Next()</li><li>c.Abort()</li><li>日志中间件</li><li>认证中间件</li><li>JWT基础</li><li>把用户信息放进context</li></ul><span id="more"></span><h2 id="Gin-middleware"><a href="#Gin-middleware" class="headerlink" title="Gin middleware"></a>Gin middleware</h2><p>Gin中间件的底层原理是：责任链与洋葱模型。在<code>Spring MVC</code>中，拦截器(<code>interceptor</code>)依赖的是反射和多层代理，而Go Gin中，中间件的本质是一个函数数组的顺序执行。</p><p>Gin的核心数据结构<code>gin.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Context <span class="keyword">struct</span> &#123;</span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">    handlers HandlersChain <span class="comment">// 本质是[]HandlerFunc，即函数切片</span></span><br><span class="line">    index   <span class="type">int8</span>           <span class="comment">// 当前执行了第几个函数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当你注册了中间件，并写了业务路由时，Gin会把他们按顺序拼接成一条流水线。例如：<code>[Logger，Auth, TargetBusinessFunc]</code>。</p><p>此时<code>index</code>的初始值是<code>-1</code>。</p><h2 id="c-Next"><a href="#c-Next" class="headerlink" title="c.Next()"></a>c.Next()</h2><div class="note info simple"><p>Gin中的<code>c.Next()</code>的作用是：继续执行后续middleware和最终handler。它前面的代码类似迁至逻辑，后面的代码类似后置逻辑，常用于日志记录、耗时统计、链路追踪等。</p></div><p>下面是Next()的代码：</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="params">(c *Context)</span></span> Next() &#123;</span><br><span class="line">c.index++</span><br><span class="line"><span class="keyword">for</span> c.index &lt; <span class="type">int8</span>(<span class="built_in">len</span>(c.handlers)) &#123;</span><br><span class="line">c.handlers[c.index](c)</span><br><span class="line">c.index++</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>原理解析：</p><p><code>c.Next()</code>只是手动递增了<code>index</code>，并用一个<code>for</code>循环继续往后执行切片里的函数。</p><ul><li>如果你在中间件里不写<code>c.Next()</code>：当前中间件函数执行完后，主流程的<code>for</code>循环以来会自动推进<code>index</code>，继续执行后面的中间件。</li><li>如果你在中间件里写了<code>c.Next()</code>:  会提前出发后面的中间件执行。当后面的中间件和业务函数全部执行完毕弹栈后，代码会回到<code>c.Next()</code>之后的部分继续执行。（洋葱模型）</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">LoggerMiddleware</span><span class="params">()</span></span> gin.HandlerFunc &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">start := time.Now()</span><br><span class="line"></span><br><span class="line">path := c.Request.URL.Path</span><br><span class="line">method := c.Request.Method</span><br><span class="line"></span><br><span class="line">c.Next()</span><br><span class="line"></span><br><span class="line">duration := time.Since(start)</span><br><span class="line">status := c.Writer.Status()</span><br><span class="line"></span><br><span class="line">log.Printf(<span class="string">&quot;%s %s %d %s&quot;</span>, method, path, status, duration)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将原来<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></pre></td><td class="code"><pre><span class="line">r := gin.New()</span><br><span class="line">r.Use(gin.Recovery())</span><br><span class="line">r.Use(LoggerMiddleware())</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">2026/06/11 08:51:41 GET /admin/users 200 0s</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">type</span> HandlerFunc <span class="function"><span class="keyword">func</span><span class="params">(* gin.Context)</span></span></span><br></pre></td></tr></table></figure><p><code>c.Next()</code> 前面的代码在请求进入时执行</p><p><code>c.Next()</code>后面的代码在后续handler执行玩抽后执行。</p><h2 id="c-Abort"><a href="#c-Abort" class="headerlink" title="c.Abort()"></a>c.Abort()</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">AuthMiddleware</span><span class="params">()</span></span> gin.HandlerFunc &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">token := c.GetHeader(<span class="string">&quot;Authorization&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> token == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">            <span class="comment">//使用刚才定义的响应</span></span><br><span class="line">response.Unauthorized(c, <span class="string">&quot;missing token&quot;</span>)</span><br><span class="line">c.Abort()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.Next()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用：在<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></pre></td><td class="code"><pre><span class="line">admin := r.Group(<span class="string">&quot;/admin&quot;</span>)</span><br><span class="line">admin.Use(AuthMiddleware())</span><br><span class="line">&#123;</span><br><span class="line">admin.GET(<span class="string">&quot;/users&quot;</span>, listUser)</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/admin/users</span><br></pre></td></tr></table></figure><p>因为在<code>Header</code>中并没有设置<code>Authorization</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">&#123;</span><br><span class="line">    <span class="string">&quot;code&quot;</span>: <span class="string">&quot;UNAUTHORIZED&quot;</span>,</span><br><span class="line">    <span class="string">&quot;message&quot;</span>: <span class="string">&quot;missing token&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>Header</code>中设置<code>Authorization</code>属性后，返回数据。</p><p><code>c.Abort()</code>会组织后续handler执行，在认证失败、权限不足、非法请求时常用。</p><div class="note info simple"><p><code>c.Abort</code>和<code>return</code>的区别：return只是结束当前函数；<code>c.Abort()</code>会告诉Gin不再执行后续<code>handler</code>。实际中通常两者一起用；先写响应，再<code>c.Abort()</code>，然后<code>return</code>。</p></div><h2 id="JWT认证中间件"><a href="#JWT认证中间件" class="headerlink" title="JWT认证中间件"></a>JWT认证中间件</h2><p>JWT是比较常用的身份验证中间件，下面通过一个例子来实际编码体验一下：</p><p>安装jwt模块</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 -u github.com/golang-jwt/jwt/v5</span><br></pre></td></tr></table></figure><p><code>pkg/utils/jwt.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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> utils</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;time&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/golang-jwt/jwt/v5&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> JWTSecret = []<span class="type">byte</span>(<span class="string">&quot;your_secret&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> MyCustomClaims <span class="keyword">struct</span> &#123;</span><br><span class="line">ID                   <span class="type">int64</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;username&quot;`</span></span><br><span class="line">jwt.RegisteredClaims        <span class="comment">// 内置的标准字段(过期时间、发行人等)</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成 Token</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GenerateToken</span><span class="params">(id <span class="type">int64</span>, username <span class="type">string</span>)</span></span> (<span class="type">string</span>, <span class="type">error</span>) &#123;</span><br><span class="line">claims := MyCustomClaims&#123;</span><br><span class="line">ID:       id,</span><br><span class="line">UserName: username,</span><br><span class="line">RegisteredClaims: jwt.RegisteredClaims&#123;</span><br><span class="line">ExpiresAt: jwt.NewNumericDate(time.Now().Add(<span class="number">2</span> * time.Hour)),</span><br><span class="line">IssuedAt:  jwt.NewNumericDate(time.Now()),</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 使用HS256算法创建token对象</span></span><br><span class="line">token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)</span><br><span class="line"><span class="comment">// 使用密钥签名获得完整token</span></span><br><span class="line"><span class="keyword">return</span> token.SignedString(JWTSecret)</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">ParseToken</span><span class="params">(tokenstr <span class="type">string</span>)</span></span> (*MyCustomClaims, <span class="type">error</span>) &#123;</span><br><span class="line">token, err := jwt.ParseWithClaims(tokenstr, &amp;MyCustomClaims&#123;&#125;, <span class="function"><span class="keyword">func</span><span class="params">(token *jwt.Token)</span></span> (<span class="keyword">interface</span>&#123;&#125;, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> JWTSecret, <span class="literal">nil</span></span><br><span class="line">&#125;)</span><br><span class="line"></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 class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> claims, ok := token.Claims.(*MyCustomClaims); ok &amp;&amp; token.Valid &#123;</span><br><span class="line"><span class="keyword">return</span> claims, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, errors.New(<span class="string">&quot;invalid token&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用方式，新建一个中间件：<code>AuthMiddleware</code>，我把它放在<code>middleware/handlers.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><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="keyword">package</span> middleware</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;strings&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev.net.cn/goweb/pkg/utils&quot;</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">AuthMiddleware</span><span class="params">()</span></span> gin.HandlerFunc &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">token := c.GetHeader(<span class="string">&quot;Authorization&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> token == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">response.Unauthorized(c, <span class="string">&quot;missing token&quot;</span>)</span><br><span class="line">c.Abort()</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">parts := strings.SplitN(token, <span class="string">&quot; &quot;</span>, <span class="number">2</span>)</span><br><span class="line"><span class="keyword">if</span> !(<span class="built_in">len</span>(parts) == <span class="number">2</span> &amp;&amp; parts[<span class="number">0</span>] == <span class="string">&quot;Bearer&quot;</span>) &#123;</span><br><span class="line">response.Unauthorized(c, <span class="string">&quot;invalid token&quot;</span>)</span><br><span class="line">c.Abort()</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">claims, err := utils.ParseToken(parts[<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">response.Unauthorized(c, <span class="string">&quot;invalid token&quot;</span>)</span><br><span class="line">c.Abort()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.Set(<span class="string">&quot;currentUserID&quot;</span>, claims.ID)</span><br><span class="line">c.Set(<span class="string">&quot;currentUsername&quot;</span>, claims.UserName)</span><br><span class="line"><span class="comment">//c.Set(&quot;claims&quot;, claims)</span></span><br><span class="line">c.Next()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在<code>r.Group(&quot;/api/d2&quot;).Use(middleware.AuthMiddleware())</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="comment">// 访问该链路需要token认证</span></span><br><span class="line">d2Group := r.Group(<span class="string">&quot;/api/d2&quot;</span>).Use(middleware.AuthMiddleware())</span><br><span class="line">&#123;</span><br><span class="line">d2Group.GET(<span class="string">&quot;/auth&quot;</span>, controllers.GetUserListHandler)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>c.Set(&quot;currentUserID&quot;,claims.ID)</code> 可以把认证结果放进Gin Context，后续的handler可以<code>c.Get(&quot;currentUserID&quot;)</code>获取它。</p>]]>
    </content>
    <id>https://dev.net.cn/learning-go-web-03/</id>
    <link href="https://dev.net.cn/learning-go-web-03/"/>
    <published>2026-06-03T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天学习的知识点：</p>
<ul>
<li>Gin middleware</li>
<li>c.Next()</li>
<li>c.Abort()</li>
<li>日志中间件</li>
<li>认证中间件</li>
<li>JWT基础</li>
<li>把用户信息放进context</li>
</ul>]]>
    </summary>
    <title>Go Web学习笔记（三）- middleware、认证、日志</title>
    <updated>2026-06-03T02: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>今天学习：</p><ul><li>ShouldBindJSON</li><li>binding tag</li><li>参数校验</li><li>统一错误响应</li><li>业务错误和参数错误区分</li></ul><span id="more"></span><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>]]>
    </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><span id="more"></span><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>启动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>]]>
    </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[<p>今天学习<code>time</code>包</p><ul><li>time.Time</li><li>time.Duration</li><li>time.Now()和time.Since()</li><li>Add</li><li>AddDate</li><li>time.Date</li><li>日期格式化</li><li>time.Parse</li><li>Unix时间戳</li><li>时间比较</li></ul><span id="more"></span><h2 id="time-Time"><a href="#time-Time" class="headerlink" title="time.Time"></a>time.Time</h2><p><code>time.Time</code>表示某个具体瞬间，精度到纳秒，通常建议把<code>time.Time</code>当值传递，而不是用<code>*time.Time</code>；它的零值是<code>0001-01-01 00:00:00 UTC</code>，判断是否初始化应该使用<code>.IsZero()</code>。</p><p>从他的零值就可以看到，它有别于时间戳，它的默认值是公元1年1月1日。</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> User <span class="keyword">struct</span> &#123;</span><br><span class="line">ID        <span class="type">int64</span></span><br><span class="line">LastLogin time.Time</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">NeedFirstLoginGuide</span><span class="params">(u User)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> u.LastLogin.IsZero()</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">TestTimeTime</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">LastLogin: time.Now(),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">t.Log(NeedFirstLoginGuide(u))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="time-Duration"><a href="#time-Duration" class="headerlink" title="time.Duration"></a>time.Duration</h2><p>表示两个时间点之间的间隔，<code>Duration</code>底层是<code>int64</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Config <span class="keyword">struct</span> &#123;</span><br><span class="line">TimeoutSeconds <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">Timeout</span><span class="params">(cfg Config)</span></span> time.Duration &#123;</span><br><span class="line">    <span class="comment">// 请不要写成 cfg.TimeoutSeconds * time.Second 单位不一样</span></span><br><span class="line"><span class="keyword">return</span> time.Duration(cfg.TimeoutSeconds) * time.Second</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>time.Second</code>不是普通整数，而是<code>time.Duration</code>的类型常量。</p></div><h2 id="time-Now-、time-Since"><a href="#time-Now-、time-Since" class="headerlink" title="time.Now()、time.Since()"></a>time.Now()、time.Since()</h2><p><code>time.Now()</code>返回当前本地时间；从<code>time.Now()</code>得到的值包含<code>wall clock</code>和<code>monotonic clock</code>。Go在计算<code>Sub</code>、<code>Since</code>、<code>Until</code>等耗时时，会优先使用单调时钟，避免系统时间被NTP或人工调整。</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">TestTimeNowAndSince</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">start := time.Now()</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(<span class="string">&quot;cost :&quot;</span>, time.Since(start))</span><br><span class="line">&#125;()</span><br><span class="line">time.Sleep(<span class="number">120</span> * time.Millisecond)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>测量耗时用<code>time.Since(start)</code>，不要用<code>time.Now().Unix() - start.Unix()</code>。</p></div><h2 id="Add：加固定时长"><a href="#Add：加固定时长" class="headerlink" title="Add：加固定时长"></a>Add：加固定时长</h2><p><code>Add(d)</code>表示在某个时间点上加一个固定的<code>Duration</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">NewCodeExpireAt</span><span class="params">()</span></span> time.Time &#123;</span><br><span class="line">    <span class="keyword">return</span> time.Now().Add(<span class="number">5</span> * time.Minute)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>Add(24 * time.Hour)</code>是加固定24小时，不等于<code>日历上的明天</code>。</p></div><h2 id="AddDate-加年、月、日"><a href="#AddDate-加年、月、日" class="headerlink" title="AddDate: 加年、月、日"></a>AddDate: 加年、月、日</h2><p><code>AddDate(years,months,days)</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">TestAddDate</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="comment">// Go的时间格式化必须使用这个特定的时间：2006-01-02 15:04:05</span></span><br><span class="line">layout := <span class="string">&quot;2006-01-02 15:04:05&quot;</span></span><br><span class="line">now := time.Now()</span><br><span class="line">fmt.Println(now.Format(layout))</span><br><span class="line">fmt.Println(NextBillingDate(now).Format(layout))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>AddDate(0,1,0)</code>和<code>Add(30 * 24 * time.Hour)</code>是不一样的，切记切记。会受到夏令时、月底等因素造成结果不同。</p></div><h2 id="time-Date-构造时间"><a href="#time-Date-构造时间" class="headerlink" title="time.Date 构造时间"></a>time.Date 构造时间</h2><p><code>time.Date</code>可以构造指定年月日时分秒纳秒和时区的事件。他会对超出范围的月、日、时、分、秒、做归一化。例如<code>10月32日</code>会变成<code>11月1日</code>。传入<code>nil</code>location会<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><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">LastDayOfMonth</span><span class="params">(year <span class="type">int</span>, month time.Month, loc *time.Location)</span></span> time.Time &#123;</span><br><span class="line"><span class="keyword">return</span> time.Date(year, month+<span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, loc)</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">TestTimeDate</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">layout := <span class="string">&quot;2006-01-02 15:04:05&quot;</span></span><br><span class="line">t.Log(LastDayOfMonth(<span class="number">2026</span>, time.June, time.Local).Format(layout))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="格式化"><a href="#格式化" class="headerlink" title="格式化"></a>格式化</h2><div class="note info simple"><p><code>Go</code>的时间格式化并不是<code>yyyy-MM-dd</code>，而是使用固定参考时间<code>2006-01-02 15:04:05</code> （记忆：01-02 下午 03:04:05 06年）</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTimeDate</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">layout := <span class="string">&quot;2006-01-02 15:04:05&quot;</span></span><br><span class="line">now := time.Now()</span><br><span class="line">t.Log(now.Format(layout))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果不想用<code>2006-01-02 15:04:05</code>，你可以使用任意一个时间，因为它是参考格式，并不是内容：<code>2026-06-19 10:00:00</code>。</p><h2 id="time-Parse-解析"><a href="#time-Parse-解析" class="headerlink" title="time.Parse 解析"></a>time.Parse 解析</h2><p><code>time.Parse(layout,value)</code>使用layout解析字符串；如果字符串没有时区信息，<code>Parse</code>默认返回UTC时间。</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">ParseUTC</span><span class="params">(s <span class="type">string</span>)</span></span> (time.Time, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> time.Parse(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>, 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">TestTimeParse</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">currentTime, err := ParseUTC(<span class="string">&quot;2026-06-18 15:00:00&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.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">t.Log(currentTime.AddDate(<span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>).Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="time-ParseInLocation本地时区解析"><a href="#time-ParseInLocation本地时区解析" class="headerlink" title="time.ParseInLocation本地时区解析"></a>time.ParseInLocation本地时区解析</h2><p>用户输入<code>2026-06-18 09:00:00</code>通常表示用户所在时区的9点，这是应使用<code>ParseInLocation</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">ParseShanghaiTime</span><span class="params">(s <span class="type">string</span>)</span></span> (time.Time, <span class="type">error</span>) &#123;</span><br><span class="line">loc, err := time.LoadLocation(<span class="string">&quot;Asia/Shanghai&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> time.Time&#123;&#125;, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> time.ParseInLocation(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>, s, loc)</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">TestTimeParseInLocation</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">t.Log(ParseShanghaiTime(<span class="string">&quot;2026-06-18 15:00:00&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Unix时间戳"><a href="#Unix时间戳" class="headerlink" title="Unix时间戳"></a>Unix时间戳</h2><p><code>time.Unix(sec,nsec)</code>用秒和纳秒构造时间，另外还提供<code>UnixMilli</code>、<code>UnixMicro</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">FromJSTimestamp</span><span class="params">(ms <span class="type">int64</span>)</span></span> time.Time &#123;</span><br><span class="line"><span class="keyword">return</span> time.UnixMilli(ms)</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">ToJSTimestamp</span><span class="params">(t time.Time)</span></span> <span class="type">int64</span> &#123;</span><br><span class="line"><span class="keyword">return</span> t.UnixMilli()</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">TestUnix</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">ms := ToJSTimestamp(time.Now())</span><br><span class="line">t.Log(ms)</span><br><span class="line"></span><br><span class="line">unixTime := FromJSTimestamp(ms)</span><br><span class="line">t.Log(unixTime.Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p>通常前端使用的都是毫秒，而Go的<code>Unix()</code>是秒。不要把毫秒吴传给<code>time.Unix(ms,0)</code>。</p></div><h2 id="时间比较"><a href="#时间比较" class="headerlink" title="时间比较"></a>时间比较</h2><p><code>Equal</code>判断两个<code>Time</code>是否表示同一瞬间，即使它们处于不同时区也可以相等。 通常建议使用<code>t.Equal(u)</code>来判断，而不是<code>t == u</code>，因为<code>==</code>还会比较<code>Location</code>和单调时钟信息。其他的比较还有<code>After</code>、<code>Before</code>、<code>Compare</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">// 是否过期</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">IsExpired</span><span class="params">(expireAt time.Time)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> time.Now().After(expireAt)</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">TestTimeEqual</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">utc := time.Date(<span class="number">2026</span>, time.June, <span class="number">18</span>, <span class="number">15</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, time.UTC)</span><br><span class="line">shanghai, _ := time.LoadLocation(<span class="string">&quot;Asia/Shanghai&quot;</span>)</span><br><span class="line">cn := time.Date(<span class="number">2026</span>, time.June, <span class="number">18</span>, <span class="number">15</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, shanghai)</span><br><span class="line">t.Logf(<span class="string">&quot;UTC: %v, Shanghai: %v, Equals %v &quot;</span>, utc, cn, utc.Equal(cn))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Round和Truncate：时间取整"><a href="#Round和Truncate：时间取整" class="headerlink" title="Round和Truncate：时间取整"></a>Round和Truncate：时间取整</h2><p><code>Time.Round(d)</code>会四舍五入到最近的<code>d</code>倍数。<code>Time.Truncate(d)</code>会向下截断到<code>d</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">TestTimeRound</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">now := time.Now()</span><br><span class="line"><span class="comment">// 2026-06-19 10:26:00</span></span><br><span class="line">t.Log(now.Round(time.Minute).Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line"><span class="comment">// 2026-06-19 10:25:00</span></span><br><span class="line">t.Log(now.Truncate(time.Minute).Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>Truncate(24 * time.Hour)</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTimeRound1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">y, m, d := time.Now().Date()</span><br><span class="line">t.Log(time.Date(y, m, d, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, time.Local).Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="time-Sleep：暂停当前goroutine"><a href="#time-Sleep：暂停当前goroutine" class="headerlink" title="time.Sleep：暂停当前goroutine"></a>time.Sleep：暂停当前goroutine</h2><p>这个和Java的一模一样了，都是让当前线（协）程睡眠一会儿。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Retry</span><span class="params">(fn <span class="keyword">func</span>()</span></span> <span class="type">error</span>) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">var</span> err <span class="type">error</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">if</span> err = fn(); err != <span class="literal">nil</span> &#123;</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">time.Sleep(time.Duration(i+<span class="number">1</span>) * time.Second)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info simple"><p><code>Time.Sleep()</code> 只阻塞当前<code>goroutine</code>，不会阻止整个进程。</p></div><h2 id="time-After-一次性超时channel"><a href="#time-After-一次性超时channel" class="headerlink" title="time.After: 一次性超时channel"></a>time.After: 一次性超时channel</h2><p><code>time.After(d)</code>返回一个<code>channel</code>，到期后会受到当前时间。未引用且停止的<code>timer</code>可以被GC回收；</p><p>select超时：</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">ReadWithTimeout</span><span class="params">(ch &lt;-<span class="keyword">chan</span> <span class="type">string</span>)</span></span> (<span class="type">string</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> v := &lt;-ch:</span><br><span class="line"><span class="keyword">return</span> v, <span class="literal">nil</span></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"><span class="keyword">return</span> <span class="string">&quot;&quot;</span>, fmt.Errorf(<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><div class="note info simple"><p>循环中频繁创建<code>time.After</code>可能带来额外分配，对于这种高频循环，更推荐复用<code>Timer</code></p></div><h2 id="NewTimer-Stop-Reset：可控的一次性的定时器"><a href="#NewTimer-Stop-Reset：可控的一次性的定时器" class="headerlink" title="NewTimer\Stop\Reset：可控的一次性的定时器"></a>NewTimer\Stop\Reset：可控的一次性的定时器</h2><p><code>Timer</code>表示一次性事件，到期后会向<code>c</code>发送当前事件，从Go 1.23开始 timer channel默认是同步、无缓冲的。<code>Stop</code>或<code>Reset</code>返回后不会在收到就得<code>stale time</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">// 复用Timer做空闲超时</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">WatchIdle</span><span class="params">(events &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">timer := time.NewTimer(<span class="number">10</span> * time.Second)</span><br><span class="line"><span class="keyword">defer</span> timer.Stop()</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;-events:</span><br><span class="line">timer.Reset(<span class="number">10</span> * time.Second)</span><br><span class="line"><span class="keyword">case</span> &lt;-timer.C:</span><br><span class="line">fmt.Println(<span class="string">&quot;idel timeout&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">&#125;</span><br></pre></td></tr></table></figure><h2 id="AfterFunc：到期后启动goroutine执行函数"><a href="#AfterFunc：到期后启动goroutine执行函数" class="headerlink" title="AfterFunc：到期后启动goroutine执行函数"></a>AfterFunc：到期后启动goroutine执行函数</h2><p><code>time.AfterFunc(d,f)</code>会等待<code>d</code>后在自己的<code>goroutine</code>里执行<code>f</code>;<code>Stop</code>不会等待已久启动的<code>f</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="comment">// 超时自动关闭资源</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">AutoClose</span><span class="params">(closeFn <span class="keyword">func</span>()</span></span>) *time.Timer &#123;</span><br><span class="line"><span class="keyword">return</span> time.AfterFunc(<span class="number">30</span>*time.Second, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">closeFn()</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>AfterFunc</code>的回调可能并发执行，如果<code>Reset</code>后旧调用还没结束，要自己用锁、Channel或者context协调。</p></div><h2 id="Ticker：周期性任务"><a href="#Ticker：周期性任务" class="headerlink" title="Ticker：周期性任务"></a>Ticker：周期性任务</h2><p><code>Ticker</code>用于周期性出发，从<code>Go 1.23</code>开始，未引用的<code>ticker</code>可以被GC回收;<code>Stop</code>不在为了帮助GC必须，但业务上想停止周期任务任需要调用。</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="comment">// 每秒上报一次心跳</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">HeartBeat</span><span class="params">(done &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">ticker := time.NewTicker(time.Second)</span><br><span class="line"><span class="keyword">defer</span> ticker.Stop()</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;-ticker.C:</span><br><span class="line">fmt.Println(<span class="string">&quot;Ping ...&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> &lt;-done:</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">&#125;</span><br></pre></td></tr></table></figure><h2 id="获取年月日、星期、ISO周"><a href="#获取年月日、星期、ISO周" class="headerlink" title="获取年月日、星期、ISO周"></a>获取年月日、星期、ISO周</h2><p><code>Time</code>提供<code>Date()</code>、<code>Clock()</code>、<code>Weekday()</code>、<code>ISOWeek()</code>、<code>YearDay()</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">TestTimeDate1</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">date := time.Date(<span class="number">2026</span>, <span class="number">6</span>, <span class="number">10</span>, <span class="number">10</span>, <span class="number">30</span>, <span class="number">0</span>, <span class="number">0</span>, time.Local)</span><br><span class="line">    <span class="comment">// date: 2026-06-10 10:30:00 +0800 CST, year: 2026, month: 6, day: 10</span></span><br><span class="line">t.Logf(<span class="string">&quot;date: %v, year: %d, month: %d, day: %d&quot;</span>, date, date.Year(), date.Month(), date.Day())</span><br><span class="line">    <span class="comment">// 2026 June 10</span></span><br><span class="line">t.Log(date.Date())</span><br><span class="line">    <span class="comment">// 10 30 0</span></span><br><span class="line">t.Log(date.Clock())</span><br><span class="line">    <span class="comment">// Wednesday</span></span><br><span class="line">t.Log(date.Weekday())</span><br><span class="line">    <span class="comment">// 161</span></span><br><span class="line">t.Log(date.YearDay())</span><br><span class="line">    <span class="comment">// 2026 24</span></span><br><span class="line">t.Log(date.ISOWeek())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://dev.net.cn/learning-go-09/</id>
    <link href="https://dev.net.cn/learning-go-09/"/>
    <published>2026-04-08T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>今天学习<code>time</code>包</p>
<ul>
<li>time.Time</li>
<li>time.Duration</li>
<li>time.Now()和time.Since()</li>
<li>Add</li>
<li>AddDate</li>
<li>time.Date</li>
<li>日期格式化</li>
<li>time.Parse</li>
<li>Unix时间戳</li>
<li>时间比较</li>
</ul>]]>
    </summary>
    <title>Go语言学习笔记（九）- time</title>
    <updated>2026-04-08T04: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><span id="more"></span><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语言中，文件操作主要分三层</p>
<ul>
<li>os : 直接和操作系统交互，比如打开文件、删除文件、创建文件、读写文件等。</li>
<li>io ：抽象I&#x2F;O行为，比如：io.Reader、io.Writer、io.Copy</li>
<li>bufio : 带缓冲的读写，适合按行读取、大文件处理、减少系统调用。</li>
</ul>]]>
    </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><span id="more"></span><figure class="highlight go"><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语言的错误，不像<code>Java</code>&#x2F;<code>Python</code>那样默认靠异常传播机制，而是一个普通返回值。Go内置的<code>error</code>类型本质上是一个<code>接口</code>。只要求实现一个方法<code>Error() string</code>；<code>nil</code>通常表示没有错误。</p>]]>
    </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><span id="more"></span><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>]]>
    </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><span id="more"></span><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>如果一个结构体拥有了某个接口定义的所有方法，那么它就实现了这个接口。</strong></p>]]>
    </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><span id="more"></span><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>]]>
    </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><span id="more"></span><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语言，但苦于当时的工作没有办法实践，而我又刚换了工作，每天都要加班到最后一班地铁，没多久，学习的内容就基本上忘光光了，今年得空，可以在学习下Go语言，手头上也有两个项目有时间去重构，那就重新学习一次吧。本次学习使用<code>go 1.26</code>版本。</p>]]>
    </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><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 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.aliyun.com/rustup&quot;</span></span><br><span class="line">RUSTUP_UPDATE_ROOT=<span class="string">&quot;https://mirrors.aliyun.com/rustup/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><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 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></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;aliyun&#x27;</span></span><br><span class="line"><span class="section">[source.aliyun]</span></span><br><span class="line"><span class="attr">registry</span> = <span class="string">&quot;sparse+https://mirrors.aliyun.com/crates.io-index/&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><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>
</feed>
