<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>认真的雪</title><link>https://blog.wjhe.top/</link><description>Recent content on 认真的雪</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Thu, 28 May 2026 10:07:03 +0800</lastBuildDate><atom:link href="https://blog.wjhe.top/index.xml" rel="self" type="application/rss+xml"/><item><title>GORM 事务中的隐藏陷阱：return nil 不代表没事发生</title><link>https://blog.wjhe.top/gorm-%E4%BA%8B%E5%8A%A1%E4%B8%AD%E7%9A%84%E9%9A%90%E8%97%8F%E9%99%B7%E9%98%B1return-nil-%E4%B8%8D%E4%BB%A3%E8%A1%A8%E6%B2%A1%E4%BA%8B%E5%8F%91%E7%94%9F/</link><pubDate>Thu, 28 May 2026 10:07:03 +0800</pubDate><guid>https://blog.wjhe.top/gorm-%E4%BA%8B%E5%8A%A1%E4%B8%AD%E7%9A%84%E9%9A%90%E8%97%8F%E9%99%B7%E9%98%B1return-nil-%E4%B8%8D%E4%BB%A3%E8%A1%A8%E6%B2%A1%E4%BA%8B%E5%8F%91%E7%94%9F/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;p&gt;在 Go + GORM 的日常开发中，事务写法通常如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 业务逻辑&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 回滚&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 更多操作...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 提交&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个模型很直觉：返回 &lt;code&gt;nil&lt;/code&gt; → 提交事务，返回 &lt;code&gt;error&lt;/code&gt; → 回滚事务。&lt;/p&gt;
&lt;p&gt;但最近踩了一个坑：&lt;strong&gt;在事务内吞掉了错误并返回 &lt;code&gt;nil&lt;/code&gt;，GORM 却报了 &lt;code&gt;commit unexpectedly resulted in rollback&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="问题复现"&gt;问题复现
&lt;/h2&gt;&lt;p&gt;简化后的伪代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 第一条 SQL：创建记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordA&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Is&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrDuplicatedKey&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 唯一键冲突，认为是可以接受的，跳过&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#75715e"&gt;// ← 问题出在这里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 第二条 SQL：创建另一条记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordB&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;预期行为：遇到重复键冲突时，跳过并提交事务。&lt;/p&gt;
&lt;p&gt;实际行为：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: commit unexpectedly resulted in rollback
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;事务直接失败，&lt;code&gt;err&lt;/code&gt; 不为 &lt;code&gt;nil&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="根因分析"&gt;根因分析
&lt;/h2&gt;&lt;p&gt;关键在于 GORM（以及底层的数据库驱动）的事务状态机：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQL 执行报错时，数据库驱动会将事务标记为 &lt;code&gt;aborted&lt;/code&gt; 状态。&lt;/strong&gt; PostgreSQL 等数据库在遇到约束违反（如唯一键冲突）后，当前事务即进入 aborted 状态，后续所有操作都会被拒绝，直到执行 ROLLBACK。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GORM 的 &lt;code&gt;Transaction()&lt;/code&gt; 方法看到你返回 &lt;code&gt;nil&lt;/code&gt;，就会尝试 &lt;code&gt;COMMIT&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;但 COMMIT 一个已经 aborted 的事务，数据库会返回错误&lt;/strong&gt;——因为事务已经被隐式回滚了，无法提交。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;时序如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;tx.Create(&amp;amp;recordA) → 唯一键冲突，PostgreSQL 标记事务为 aborted
return nil → GORM 尝试 COMMIT
COMMIT → 数据库拒绝：事务已经是 aborted 状态
 → GORM 收到错误，报 &amp;#34;commit unexpectedly resulted in rollback&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;核心问题：你吞掉了错误，但事务状态并没有&amp;quot;恢复原状&amp;quot;。数据库驱动已经记住了这个错误。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案
&lt;/h2&gt;&lt;h3 id="思路把可预期的冲突包装成一个特殊的错误类型在事务外处理"&gt;思路：把&amp;quot;可预期的冲突&amp;quot;包装成一个特殊的错误类型，在事务外处理
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;第一步：定义业务错误类型&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ErrDuplicated 重复键冲突的业务错误&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ErrDuplicated&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;record already exists&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第二步：事务内返回该错误，而不是 return nil&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordA&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Is&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrDuplicatedKey&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ErrDuplicated&lt;/span&gt; &lt;span style="color:#75715e"&gt;// ← 返回业务错误，让事务正常回滚&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordB&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第三步：在事务外部区分处理&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Is&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ErrDuplicated&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 重复键冲突是可预期的，正常处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;记录已存在，跳过&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 其他错误正常上报&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="为什么这样是对的"&gt;为什么这样是对的？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;事务内部：任何错误都如实返回，让 GORM 执行 ROLLBACK，保持事务状态干净。&lt;/li&gt;
&lt;li&gt;事务外部：通过错误类型区分&amp;quot;业务可接受的冲突&amp;quot;和&amp;quot;真正的失败&amp;quot;。&lt;/li&gt;
&lt;li&gt;不会触碰数据库事务状态机的&amp;quot;禁区&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;做法&lt;/th&gt;
					&lt;th&gt;结果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;事务内吞掉 DB 错误，返回 &lt;code&gt;nil&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;事务状态已 aborted，COMMIT 必然失败&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事务内返回自定义错误，外部判断处理&lt;/td&gt;
					&lt;td&gt;事务正常 ROLLBACK，业务逻辑正确分流&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;记住：GORM 事务中，&lt;code&gt;return nil&lt;/code&gt; 永远意味着&amp;quot;请提交&amp;quot;。如果你不能提交，就不要返回 &lt;code&gt;nil&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即使某个错误在业务上是&amp;quot;可以接受的&amp;quot;，也要在事务外部去接受它，而不是在事务内部悄悄吞掉它。&lt;/p&gt;</description></item><item><title>rhel 9 系统 Docker 运行 CentOS yum 命令卡死</title><link>https://blog.wjhe.top/rhel-9-%E7%B3%BB%E7%BB%9F-docker-%E8%BF%90%E8%A1%8C-centos-yum-%E5%91%BD%E4%BB%A4%E5%8D%A1%E6%AD%BB/</link><pubDate>Tue, 03 Feb 2026 10:02:00 +0800</pubDate><guid>https://blog.wjhe.top/rhel-9-%E7%B3%BB%E7%BB%9F-docker-%E8%BF%90%E8%A1%8C-centos-yum-%E5%91%BD%E4%BB%A4%E5%8D%A1%E6%AD%BB/</guid><description>&lt;p&gt;CentOS Stream 9、Rocky Linux 9 等 rhel 9 系统。在 Docker 容器的 CentOS7 镜像执行 yum 操作 特别慢。
是因为 open files 太大了。&lt;/p&gt;
&lt;p&gt;先查看之前的 ulimit&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run -it --tty --network host --name centos7 centos:centos7.9.2009 /bin/bash
ulimit -a
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;输出&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30537
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 1073741816
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;替换源&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for f in /etc/yum.repos.d/*.repo
do
 mv -f &amp;#34;$f&amp;#34; &amp;#34;${f}.backup&amp;#34;
done
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.huaweicloud.com/artifactory/os-conf/centos/centos-7.repo

yum makecache -y 
yum update -y 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;直接卡死在 &lt;code&gt;Updating : glibc-2.17-326.el7_9.3.x86_64&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;现在我们重新加上 ulimit&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run -it --tty --network host --name centos7 --ulimit &amp;#34;nofile=1024:1048576&amp;#34; centos:centos7.9.2009 /bin/bash
ulimit -a
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;输出&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30537
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行上面的步骤，就不会卡死了。&lt;/p&gt;
&lt;p&gt;参考
&lt;a class="link" href="https://github.com/moby/moby/issues/45838" target="_blank" rel="noopener"
 &gt;https://github.com/moby/moby/issues/45838&lt;/a&gt;&lt;/p&gt;</description></item><item><title>postgresql 常用命令</title><link>https://blog.wjhe.top/postgresql-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><pubDate>Thu, 15 May 2025 15:45:00 +0800</pubDate><guid>https://blog.wjhe.top/postgresql-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><description>&lt;ol&gt;
&lt;li&gt;pg_dump
pg_dump 导出 同时 去掉 public 符号&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pg_dump -d d_test --inserts --column-inserts --quote-all-identifiers -T public.goose_db_version -T public.goose_db_version_id_seq | sed -e &amp;#39;/^--.*/d&amp;#39; -e &amp;#39;/^SET /d&amp;#39; -e &amp;#39;/^[[:space:]]*$/d&amp;#39; -e &amp;#39;/^SELECT pg_catalog./d&amp;#39; -e &amp;#39;/^ALTER TABLE .* OWNER TO &amp;#34;postgres&amp;#34;;/d&amp;#39; -e &amp;#39;s/&amp;#34;public&amp;#34;\.//g&amp;#39; &amp;gt; /tmp/d_test.sql
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>golang go常用命令</title><link>https://blog.wjhe.top/golang-go%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><pubDate>Thu, 24 Apr 2025 16:29:00 +0800</pubDate><guid>https://blog.wjhe.top/golang-go%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><description>&lt;p&gt;查看某个包的所有版本&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;go list -m -versions &amp;lt;pkg&amp;gt;&lt;/code&gt;
&lt;code&gt;go list -m -versions github.com/go-kratos/kratos/v2&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;查看某个二进制的构建信息&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;go version -m &amp;lt;path&amp;gt;&lt;/code&gt;
&lt;code&gt;go version -m /usr/bin/go/bin/go&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>Nginx 缓存 文件名和路径计算</title><link>https://blog.wjhe.top/nginx-%E7%BC%93%E5%AD%98-%E6%96%87%E4%BB%B6%E5%90%8D%E5%92%8C%E8%B7%AF%E5%BE%84%E8%AE%A1%E7%AE%97/</link><pubDate>Fri, 13 Dec 2024 09:28:00 +0800</pubDate><guid>https://blog.wjhe.top/nginx-%E7%BC%93%E5%AD%98-%E6%96%87%E4%BB%B6%E5%90%8D%E5%92%8C%E8%B7%AF%E5%BE%84%E8%AE%A1%E7%AE%97/</guid><description>&lt;p&gt;缓存键 (proxy_cache_key): proxy_cache_key 指定了 NGINX 如何根据请求生成一个缓存键。
&lt;strong&gt;缓存键 (proxy_cache_key)&lt;/strong&gt;
proxy_cache_key 指定了 NGINX 如何根据请求生成一个缓存键。
&lt;strong&gt;哈希计算&lt;/strong&gt;
对缓存键进行哈希处理，常使用 MD5 算法。
例如，如果缓存键是 example.com/image.png , 进行 MD5 哈希后会得到一个 32 字符的哈希值（c877fe850c17cb1a4d0b158dd2b64cfb）。
这里有一种特殊情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当第一次缓存的时候，得到的哈希，就是MD5(example.com/image.png)&lt;/li&gt;
&lt;li&gt;第二次缓存，如果源站有 Vary 响应头 并且 Vary 的值 与第一次缓存的不一致，则会通过 Vary 的值 计算一个新的哈希，如果没有 Vary 响应头，则不会计算新的哈希。
就是说，如果源站响应头没有 Vary，则每次的哈希都是一样的，缓存的是同一个文件，否则会生成新的缓存文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;就是说，无论第一次的 Accept-Encoding 是什么，都不会被Accept-Encoding影响，第二次会因为Accept-Encoding影响。
第一次缓存算法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 直接对缓存键哈希&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// cacheKey: 缓存键&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getCacheFileHash&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cacheKey&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keyHash&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;md5&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sum&lt;/span&gt;([]byte(&lt;span style="color:#a6e22e"&gt;cacheKey&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hex&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;EncodeToString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;keyHash&lt;/span&gt;[:])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第二次缓存算法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// 计算缓存文件哈希
//
// cacheKey: 缓存键
// vary: 响应头的Vary字段 Accept-Encoding | Accept-Charset | Accept-Language | other
// varyValue: deflate, gzip
func getCacheFileHash(cacheKey string, vary string, varyValue string) string {
 // 转小写 并且去除两边空格
 vary = strings.TrimSpace(strings.ToLower(vary))
 
 // 通过逗号或者空格分隔，然后再通过逗号join
 // 先转小写 并且去除两边空格
 varyValue = strings.TrimSpace(strings.ToLower(varyValue))
 varyValueList := strings.FieldsFunc(varyValue, func(r rune) bool {
 return r == &amp;#39;,&amp;#39; || unicode.IsSpace(r)
 })
 // 再去掉空白字符
 valueList := make([]string, 0, len(varyValueList))
 for _, s := range varyValueList {
 if s == &amp;#34;&amp;#34; {
 continue
 }
 valueList = append(valueList, s)
 }
 varyValue = strings.Join(valueList, &amp;#34;,&amp;#34;)
 // 开始 计算
 // hexEncode(md5(md5(cacheKey) + vary + &amp;#34;:&amp;#34; + varyValue + &amp;#34;\r\n&amp;#34;))
 // example.com/image.png Accept-Encoding deflate, gzip
 // = hexEncode(md5(md5(&amp;#34;example.com/image.png&amp;#34;) + &amp;#34;accept-encoding:deflate,gzip\r\n&amp;#34;))
 buf := bytes.NewBuffer(nil)
 
 keyHash := md5.Sum([]byte(cacheKey))
 buf.Write(keyHash[:])
 buf.WriteString(vary)
 buf.WriteString(&amp;#34;:&amp;#34;)
 buf.WriteString(varyValue)
 buf.WriteString(&amp;#34;\r\n&amp;#34;)
 newHash := md5.Sum(buf.Bytes())
 return hex.EncodeToString(newHash[:])
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;路径层次结构&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 proxy_cache_path 配置中，我们可以看到 levels=1:2。这表示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;生成的缓存路径会被拆分成两级目录结构。&lt;/li&gt;
&lt;li&gt;第一层目录使用哈希值的倒数第一位字符（b）。&lt;/li&gt;
&lt;li&gt;第二层目录使用哈希值的倒数第三位和倒数第二位字符（cf）。&lt;/li&gt;
&lt;li&gt;最终的缓存文件名将使用哈希值（c877fe850c17cb1a4d0b158dd2b64cfb）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，基于缓存键 example.com/image.png，假设其哈希值为 c877fe850c17cb1a4d0b158dd2b64cfb，则缓存文件的路径为：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/var/cache/nginx/b/cf/c877fe850c17cb1a4d0b158dd2b64cfb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;nginx反向代理缓存实现，md5加密规则，gzip压缩问题：[https://blog.51cto.com/u_14210437/11868562][1]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;源代码：[https://github.com/nginx/nginx/blob/master/src/http/ngx_http_file_cache.c][2]&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>dnsdist dns负载均衡器</title><link>https://blog.wjhe.top/dnsdist-dns%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%99%A8/</link><pubDate>Fri, 28 Jun 2024 17:51:00 +0800</pubDate><guid>https://blog.wjhe.top/dnsdist-dns%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%99%A8/</guid><description>&lt;p&gt;&lt;strong&gt;什么是 dnsdist&lt;/strong&gt;
dnsdist 是一个现代化的 UNIX 守护进程,主要用于 DNS 流量的负载均衡和流量整形。它是由 PowerDNS 团队开发的一款开源软件,可以帮助您构建高性能、高可用的 DNS 基础设施。&lt;/p&gt;
&lt;p&gt;dnsdist 的主要功能包括:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;负载均衡：dnsdist 可以将 DNS 查询均匀地分配到多个后端服务器上,提高整体的处理能力。它支持多种负载均衡算法,如 leastOutstanding、firstAvailable、wrandom 等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;流量整形：dnsdist 可以根据各种规则对 DNS 流量进行限制和阻挡,比如限制每个客户端的查询速率、阻挡恶意流量等。这有助于保护您的 DNS 基础设施免受 DDoS 攻击。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动态规则生成：dnsdist 可以根据实时监控的流量情况,自动生成并应用动态的阻挡规则,以应对突发的攻击或异常流量。这使得您的 DNS 系统能够更好地抵御各种安全威胁。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;灵活的配置：dnsdist 的配置是通过 Lua 脚本来实现的,这使得它非常灵活和可扩展。您可以根据自己的需求编写定制的 Lua 脚本,实现各种复杂的流量管理策略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缓存: dnsdist 实现了一个简单但高效的数据包缓存,可以大幅提高查询响应速度,减少带宽使用和服务器负载。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;##安装##&lt;/p&gt;
&lt;p&gt;当前最新版是1.9.x
这里以 centos9 安装 dnsdist 1.9.x为例&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dnf install epel-release &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -o /etc/yum.repos.d/powerdns-dnsdist-19.repo https://repo.powerdns.com/repo-files/el-dnsdist-19.repo &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dnf install dnsdist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;编辑配置文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vim /etc/dnsdist/dnsdist.conf&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-lua" data-lang="lua"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;local&lt;/span&gt; downstreamServers &lt;span style="color:#f92672"&gt;=&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#34;10.20.0.50&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;10.20.0.51&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;addCapabilitiesToRetain(&lt;span style="color:#e6db74"&gt;&amp;#34;CAP_SYS_ADMIN&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;addCapabilitiesToRetain(&lt;span style="color:#e6db74"&gt;&amp;#34;CAP_NET_ADMIN&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 设置访问控制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;setACL(&lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 设置UDP缓存区大小&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;setUDPSocketBufferSizes(&lt;span style="color:#ae81ff"&gt;134217728&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;134217728&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 设置服务器策略&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;setServerPolicy(leastOutstanding)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 设置缓存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;local&lt;/span&gt; pc &lt;span style="color:#f92672"&gt;=&lt;/span&gt; newPacketCache(&lt;span style="color:#ae81ff"&gt;10000000&lt;/span&gt;, {maxTTL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10000000&lt;/span&gt;, minTTL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, temporaryFailureTTL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;, staleTTL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;, dontAge&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, cookieHashing&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;getPool(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;):setCache(pc)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 添加本地监听和下游服务器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;addServers&lt;/span&gt;(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; k, v &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; ipairs(downstreamServers) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newServer({address&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v, name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;dns&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;..&lt;/span&gt;k, checkInterval&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, checkType&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ANY&amp;#34;&lt;/span&gt;, checkName&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;, useProxyProtocol&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;addListeners&lt;/span&gt;(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; addLocal(&lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0:53&amp;#34;&lt;/span&gt;, {reusePort&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, sockets&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;local&lt;/span&gt; cpuCores &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tonumber(io.popen(&lt;span style="color:#e6db74"&gt;&amp;#34;nproc&amp;#34;&lt;/span&gt;):read(&lt;span style="color:#e6db74"&gt;&amp;#34;*n&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; cpuCores &lt;span style="color:#f92672"&gt;or&lt;/span&gt; cpuCores &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpuCores &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, cpuCores &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; addListeners(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; addServers(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 以下配置内容可选&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 启用统计和监控界面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;--[[
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;webserver(&amp;#34;0.0.0.0:8083&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;setWebserverConfig({password=hashPassword(&amp;#34;3gR#v8P!xWzL9kDt&amp;#34;), apiKey=hashPassword(&amp;#34;!dNVcJHks!xWdSKs&amp;#34;), acl=&amp;#34;0.0.0.0/0&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;--]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 设置QPS限制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;--[[
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;addAction(NotRule(MaxQPSRule(300000)), DropAction())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;--]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 开启动态规则过滤&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;--[[
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;local bpf = newBPFFilter({ipv4MaxItems=10240, ipv6MaxItems=10240, qnamesMaxItems=1024, cidr4MaxItems=1024,cidr6MaxItems=1024,})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;setDefaultBPFFilter(bpf)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;local dbr = dynBlockRulesGroup()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;dbr:setQueryRate(200, 1, &amp;#34;Exceeded query rate&amp;#34;, 20)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;dbr:setRCodeRate(DNSRCode.NXDOMAIN, 20, 10, &amp;#34;Exceeded NXD rate&amp;#34;, 60)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;dbr:setRCodeRate(DNSRCode.SERVFAIL, 20, 10, &amp;#34;Exceeded ServFail rate&amp;#34;, 60)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;dbr:setQTypeRate(DNSQType.ANY, 5, 10, &amp;#34;Exceeded ANY rate&amp;#34;, 60)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;dbr:setResponseByteRate(10000, 10, &amp;#34;Exceeded resp BW rate&amp;#34;, 60)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;function maintenance()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; dbr:apply()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; --]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;启动dnsdist&lt;/p&gt;
&lt;p&gt;&lt;code&gt;systemctl start dnsdist&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;设置自启&lt;/p&gt;
&lt;p&gt;&lt;code&gt;systemctl enable dnsdist&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="性能调优"&gt;性能调优
&lt;/h2&gt;&lt;p&gt;调整 Linux 的网络参数：在sysctl.conf 添加以下内容，然后执行 sysctl -p 加载&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.core.rmem_max = 134217728
net.core.rmem_default=1048576
net.core.wmem_max = 134217728
net.core.wmem_default = 1048576
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>使用 File Watcher 和 goimports-reviser 自动格式化 Golang 代码</title><link>https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/</link><pubDate>Sat, 28 Oct 2023 15:51:00 +0800</pubDate><guid>https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/</guid><description>&lt;p&gt;goimports-reviser 是一个管理 import 分组的工具，当前也已支持（ gofmt ）格式化代码。github 地址 [https://github.com/incu6us/goimports-reviser][https_github.com_incu6us_goimports-reviser]&lt;/p&gt;
&lt;p&gt;这里来&lt;/p&gt;
&lt;h2 id="安装-goimports-reviser"&gt;安装 goimports-reviser
&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;go install -v github.com/incu6us/goimports-reviser/v3@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="设置-file-watcher"&gt;设置 File Watcher
&lt;/h2&gt;&lt;p&gt;Goland 打开 ***文件 →设置 → 工具→File Watcher ***&lt;/p&gt;
&lt;p&gt;新增一个自定义 ，参数如下&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;配置项&lt;/th&gt;
					&lt;th&gt;值&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;文件类型&lt;/td&gt;
					&lt;td&gt;Go文件&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;作用域&lt;/td&gt;
					&lt;td&gt;当前文件&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;程序&lt;/td&gt;
					&lt;td&gt;goimports-reviser&lt;/td&gt;
					&lt;td&gt;goimports-reviser 可执行程序路径&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实参&lt;/td&gt;
					&lt;td&gt;-rm-unused -format -use-cache -company-prefixes=git.test.com $FilePath$&lt;/td&gt;
					&lt;td&gt;这里没有加 -set-alias ，根据个人需求，prefixes是私有库地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;要刷新的输出路径&lt;/td&gt;
					&lt;td&gt;$FileName$&lt;/td&gt;
					&lt;td&gt;这个一定要加，不然会容易出现 “文件缓存冲突”&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自动保存编辑的文件以触发观察程序&lt;/td&gt;
					&lt;td&gt;false&lt;/td&gt;
					&lt;td&gt;取消勾选，否则容易出现还没编辑完成就被格式化了&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如图所示&lt;/p&gt;
&lt;p&gt;&lt;img alt="2023-11-02_17-55.png" class="gallery-image" data-flex-basis="280px" data-flex-grow="116" height="726" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/2023-11-02_17-55-2.png" srcset="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/2023-11-02_17-55-2_hu_78b7da0c5f0ed269.png 800w, https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/2023-11-02_17-55-2.png 847w" width="847"&gt;&lt;/p&gt;
&lt;h2 id="取消保存时的操作"&gt;取消保存时的操作
&lt;/h2&gt;&lt;p&gt;Goland保存时会默认&lt;em&gt;重新格式化代码&lt;/em&gt;和*优化 import ，*因为 goimports-reviser 会帮我们做 goimports 和 gofmt 操作，我们保存时没必要再重新执行这些功能。这个根据个人情况修改&lt;/p&gt;
&lt;p&gt;Goland 打开 ***文件 →******设置 → 工具→保存时的操作 ，***取消勾选 &lt;strong&gt;重新格式化代码、优化 import&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如图所示&lt;/p&gt;
&lt;p&gt;&lt;img alt="image2023-7-2_15-16-9.png" class="gallery-image" data-flex-basis="276px" data-flex-grow="115" height="831" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/image2023-7-2_15-16-9-2.png" srcset="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/image2023-7-2_15-16-9-2_hu_ff84064497f0ade1.png 800w, https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/image2023-7-2_15-16-9-2.png 956w" width="956"&gt;&lt;/p&gt;</description></item><item><title>Goland (Jetbains IDEA) 在Windows下测试用例出现空格、未进行任何测试（no test were run）、输出被截断</title><link>https://blog.wjhe.top/goland-no-test-were-run/</link><pubDate>Thu, 01 Sep 2022 16:49:00 +0800</pubDate><guid>https://blog.wjhe.top/goland-no-test-were-run/</guid><description>&lt;p&gt;Windows下Goland 等Jetbains IDEA运行测试用例时，出现如下问题之一&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;未进行任何测试（no test were run）&lt;/li&gt;
&lt;li&gt;输出文本被截断&lt;/li&gt;
&lt;li&gt;多出空格&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最开始以为是用的fmt.Print打印，换成t.Log还是不行，换成Idea也不行，网站一直没找到答案&lt;/p&gt;
&lt;p&gt;最后参照[Goland测试用例打印出现空格和显示不全问题 (zdzyzy.com)][Goland_ _zdzyzy.com]&lt;br&gt;
修复成功，这里记录一下。&lt;/p&gt;
&lt;p&gt;先 双击shift 输入registry&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-4.png" class="gallery-image" data-flex-basis="235px" data-flex-grow="98" height="687" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/goland-no-test-were-run/image-4.png" width="674"&gt;&lt;/p&gt;
&lt;p&gt;然后点击 注册表（Registry）&lt;/p&gt;
&lt;p&gt;找到 go.run.processes.with.pty 去掉勾选&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-5.png" class="gallery-image" data-flex-basis="199px" data-flex-grow="83" height="713" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/goland-no-test-were-run/image-5.png" width="594"&gt;&lt;/p&gt;
&lt;p&gt;点击关闭，然后重启Goland&lt;/p&gt;</description></item><item><title>Golang三色标记法</title><link>https://blog.wjhe.top/golang-gc/</link><pubDate>Fri, 27 Aug 2021 16:25:55 +0800</pubDate><guid>https://blog.wjhe.top/golang-gc/</guid><description>&lt;p&gt;三个集合，颜色分别为白、灰、黑；&lt;/p&gt;
&lt;p&gt;1.把所有对象放入白色集合；&lt;/p&gt;
&lt;p&gt;2.遍历GC-Roots直接可访问到的节点，将其从白色集合放入灰色集合；&lt;/p&gt;
&lt;p&gt;3.遍历灰色集合，将灰色集合引用的对象从白色放入灰色集合，最后将自己放入黑色集合；&lt;/p&gt;
&lt;p&gt;4.重复3操作,直到灰色集合中的元素为空;&lt;/p&gt;
&lt;p&gt;5.通过写屏障检测对象变化，重复以上操作；&lt;/p&gt;
&lt;p&gt;6.回收白色集合中的对象&lt;/p&gt;</description></item><item><title>咪咕音乐 3D音乐解密</title><link>https://blog.wjhe.top/migu3d-decrypt/</link><pubDate>Sat, 31 Jul 2021 19:16:00 +0800</pubDate><guid>https://blog.wjhe.top/migu3d-decrypt/</guid><description>&lt;h2 id="一下载软件"&gt;一、下载软件
&lt;/h2&gt;&lt;p&gt;咪咕音乐的3D音乐只能用咪咕音乐听，有没有什么办法能让我们在任何APP都能听了，最近发现一个软件可以直接解密&lt;/p&gt;
&lt;p&gt;地址如下：&lt;/p&gt;
&lt;p&gt;[https://gitee.com/lagoushangdiao/migu3d-decrypt/releases/latest][https_gitee.com_lagoushangdiao_migu3d-decrypt_releases_latest]&lt;/p&gt;
&lt;h2 id="二使用"&gt;二、使用
&lt;/h2&gt;&lt;p&gt;使用方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A migu 3d audio decrypt cli.

Usage:
 migu3d-decrypt [command]

Available Commands:
 completion Generate the autocompletion script for the specified shell
 decrypt Decrypt local file, only support [mg3d|m4a|wav] format
 help Help about any command
 serve Start a http server
 update Check for update

Flags:
 -h, --help help for migu3d-decrypt
 -t, --toggle Help message for toggle
 -v, --version version for migu3d-decrypt

Use &amp;quot;migu3d-decrypt [command] --help&amp;quot; for more information about a command.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里解密 梁静茹-勇气.wav，歌曲ID为3403&lt;/p&gt;
&lt;p&gt;下载地址&lt;/p&gt;
&lt;p&gt;链接：[https://pan.baidu.com/s/1z7hSjPlmcztEJkZW5lUvEA][https_pan.baidu.com_s_1z7hSjPlmcztEJkZW5lUvEA]&lt;br&gt;
提取码：6666&lt;/p&gt;
&lt;p&gt;解密命令如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接解密&lt;br&gt;
&lt;code&gt;./migu3d-decrypt.exe decrypt ./梁静茹-勇气.wav&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用歌曲id解密&lt;br&gt;
&lt;code&gt;./migu3d-decrypt.exe decrypt --song-id 3403 ./梁静茹-勇气.wav&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用filekey解密&lt;br&gt;
&lt;code&gt;./migu3d-decrypt.exe decrypt --file-key 29B55E6F0BDF79EA5D7EDBCA4C2B7404 ./梁静茹-勇气.wav&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;![image-6.png][]解密结果&lt;/p&gt;
&lt;h2 id="三搭建http服务"&gt;三、搭建HTTP服务
&lt;/h2&gt;&lt;p&gt;首先下载对应平台的软件，我这里是windows&lt;/p&gt;
&lt;p&gt;然后运行软件，命令如下&lt;/p&gt;
&lt;p&gt;./migu3d-decrypt.exe serve&lt;/p&gt;
&lt;p&gt;再复制http地址（内网or公网大家自行更改）&lt;/p&gt;
&lt;p&gt;比如 &lt;code&gt;http://127.0.0.1:10085&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="migu.png" class="gallery-image" data-flex-basis="180px" data-flex-grow="75" height="1188" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/migu3d-decrypt/migu.png" srcset="https://blog.wjhe.top/migu3d-decrypt/migu_hu_d668388da31b91ad.png 800w, https://blog.wjhe.top/migu3d-decrypt/migu.png 894w" width="894"&gt;&lt;/p&gt;
&lt;p&gt;确定保存就可以了，然后就可以播放or下载3D音乐了&lt;/p&gt;</description></item><item><title>（1）Redis SDS 简单动态字符串</title><link>https://blog.wjhe.top/redis-sds/</link><pubDate>Mon, 31 May 2021 10:11:19 +0800</pubDate><guid>https://blog.wjhe.top/redis-sds/</guid><description>&lt;p&gt;Redis没有使用C语言传统的字符串表示，而是构建了一种简单动态字符串（SDS）的类型，C字符串只会作为字符串字面量在一些无需对字符串值进行修改的地方，比如打印日志。&lt;/p&gt;
&lt;p&gt;SDS用来保存数据库中的字符串值、用作缓冲区：AOF缓冲区，客户端状态缓冲区。&lt;/p&gt;
&lt;p&gt;SDS定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct sdshdr { int len;//记录buf数组中已使用字节的数量，等于SDS所保存字符串的长度 int free;//记录buf数组中未使用字节的数量 char buf[];//字节数组，用于保存字符串}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SDS遵循C字符串以空字符结尾的惯例，保留空字符的1字节空间，不计算在len属性里面，并且为空字符分配额外的1字节空间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SDS与C字符串的区别&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;常数复杂度获取字符串长度&lt;/li&gt;
&lt;li&gt;杜绝缓冲区溢出&lt;/li&gt;
&lt;li&gt;减少修改字符串时带来的内存重分配次数&lt;/li&gt;
&lt;li&gt;二进制安全&lt;/li&gt;
&lt;li&gt;兼容部分C字符串函数&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;常数复杂度获取字符串长度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C语言：遍历整个字符串&lt;br&gt;
SDS：读取len属性&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;杜绝缓冲区溢出&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C语言：不记录自身长度，没有判断空间是否满足修改后的大小&lt;br&gt;
SDS：api会先检查SDS的空间是否满足修改所需的需求&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;减少修改字符串时带来的内存重分配次数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C语言拼接操作：先通过内存分配扩展底层数组空间大小&lt;br&gt;
C语言截断操作：截断后释放不再使用的那部分空间&lt;br&gt;
SDS：空间预分配和惰性空间释放&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;空间预分配&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果对SDS修改后，len小于1MB，将会分配len属性同样的大小的未使用空间，free = len,buf的长度为len + free + 1byte = 2 * len + 1byte，如果len大于等于1MB，将会分配1MB的未使用空间,free = 1MB,buf的长度为len+free+1byte=len+1MB+1byte&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;惰性空间释放&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;惰性空间释放用于优化SDS的字符串缩短操作，当缩短字符串时，程序并不立即使用内存重新分配来回收缩短后多出来的字节，而是使用free记录下来，避免了缩短字符串所需的的内存重新分配操作，并且为将来可能有得增长操作提供了优化。同时，SDS也提供了相应的API，在我们有需要时，真正的释放SDS的未使用空间，不用担心内存浪费。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二进制安全&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C语言：字符必须符合某种编码，并且除了字符串末尾，不能包含空字符，所以C字符串只能保存文本数据&lt;br&gt;
SDS：使用buf来存字节数组，len来判断字符串长度，字符串是否结束，所以可以保存文本数据和二进制数据&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;兼容部分C字符串函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SDS的buf因为以空字符结尾，所以可以兼容部分C字符串函数&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;C字符串&lt;/th&gt;
					&lt;th&gt;SDS&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;获取字符串长度的复杂度为 &lt;em&gt;O(N)&lt;/em&gt;&lt;/td&gt;
					&lt;td&gt;获取字符串长度的复杂度为 &lt;em&gt;O(1)&lt;/em&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;API是不安全的，可能会造成缓冲区溢出&lt;/td&gt;
					&lt;td&gt;API是安全的，不会造成缓冲区溢出&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;修改字符串长度N次必然需要执行N次内存重分配&lt;/td&gt;
					&lt;td&gt;修改字符串长度N次最多需要执行N次内存重分配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;只能保存文本数据&lt;/td&gt;
					&lt;td&gt;可以保存文本或者二进制数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可以使用所有 &lt;code&gt;string.h&lt;/code&gt; 库中的函数&lt;/td&gt;
					&lt;td&gt;可以使用一部分 &lt;code&gt;string.h&lt;/code&gt; 库中的函数&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;C字符串和SDS之间的区别&lt;/p&gt;</description></item><item><title>fiddler 安卓证书，安装到系统证书</title><link>https://blog.wjhe.top/fiddler-%E5%AE%89%E5%8D%93%E8%AF%81%E4%B9%A6%E5%AE%89%E8%A3%85%E5%88%B0%E7%B3%BB%E7%BB%9F%E8%AF%81%E4%B9%A6/</link><pubDate>Mon, 16 Nov 2020 17:34:00 +0800</pubDate><guid>https://blog.wjhe.top/fiddler-%E5%AE%89%E5%8D%93%E8%AF%81%E4%B9%A6%E5%AE%89%E8%A3%85%E5%88%B0%E7%B3%BB%E7%BB%9F%E8%AF%81%E4%B9%A6/</guid><description>&lt;p&gt;主要引用[https://blog.csdn.net/djzhao627/article/details/102812783][https_blog.csdn.net_djzhao627_article_details_102812783]&lt;/p&gt;
&lt;p&gt;安卓手机需要root&lt;/p&gt;
&lt;p&gt;打开fiddler,访问xxx.xxx.xxx.xxx:8888下载证书&lt;/p&gt;
&lt;p&gt;1.把cer证书转换为pem证书&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl x509 -inform DER -in FiddlerRoot.cer -out cacert.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.进行MD5的hash显示&lt;/p&gt;
&lt;p&gt;查看openssl版本 &lt;code&gt;openssl version&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;openssl版本在1.0以上的版本的执行这一句&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl x509 -inform PEM -subject_hash_old -in cacert.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;openssl版本在1.0以下的版本的执行这一句&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl x509 -inform PEM -subject_hash -in cacert.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.文件重命名 为hash加.0 ,我这里hash为e5c3944b&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mv cacert.pem e5c3944b.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.将证书放入/system/etc/security/cacerts，再重启手机，就可以看到系统证书里面有该证书了&lt;/p&gt;</description></item><item><title>虾米音乐fiddler显示网络请求</title><link>https://blog.wjhe.top/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/</link><pubDate>Mon, 16 Nov 2020 17:20:00 +0800</pubDate><guid>https://blog.wjhe.top/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/</guid><description>&lt;p&gt;阿里系的app，用的mtopsdk，默认Spdy是开启的，fiddler是不显示网络请求的。&lt;/p&gt;
&lt;p&gt;今天偶然搜到可以用frida hook，把Spdy关闭。这里记一下过程：&lt;/p&gt;
&lt;p&gt;本文主要引用[&lt;em&gt;https://www.jianshu.com/p/fa14e8063f79&lt;/em&gt;][https_www.jianshu.com_p_fa14e8063f79]的过程&lt;/p&gt;
&lt;p&gt;要求 安卓手机需要已经root&lt;/p&gt;
&lt;p&gt;首先安装frida模块和frida-tools工具,需要python3&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip3 install fridapip3 install frida-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果下载比较慢，可以使用国内清华大学镜像&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip3 install frida -i https://pypi.mirrors.ustc.edu.cn/simple/pip3 install frida-tools -i https://pypi.mirrors.ustc.edu.cn/simple/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后下载frida-server，这个是要运行在安卓手机上的，首先查看cpu类型&lt;/p&gt;
&lt;p&gt;命令如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb shellgetprop ro.product.cpu.abi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我这里是arm64&lt;/p&gt;
&lt;p&gt;&lt;img alt="server.png" class="gallery-image" data-flex-basis="414px" data-flex-grow="172" height="611" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/server.png" srcset="https://blog.wjhe.top/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/server_hu_36d90dc69544d163.png 800w, https://blog.wjhe.top/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/server.png 1055w" width="1055"&gt;&lt;/p&gt;
&lt;p&gt;frida-server 下载地址是[https://github.com/frida/frida/releases][https_github.com_frida_frida_releases] 目前最新版是14.0.8&lt;/p&gt;
&lt;p&gt;我下载的是 [frida-server-14.0.8-android-arm64.xz][]&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-1024x593.png" class="gallery-image" data-flex-basis="1215px" data-flex-grow="506" height="63" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/image-1024x593.png" width="319"&gt;&lt;/p&gt;
&lt;p&gt;下载后，把文件解压，再push到安卓手机上&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb push frida-server-14.0.8-android-arm64 /data/local/tmp/frida-server14.0.8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后adb shell进入虚拟机：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /data/local/tmp/ //进入frida-server所在目录chmod 777 frida-server14.0.8 //赋予权限./frida-server14.0.8 &amp;amp; //启动运行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新打开一个cmd窗口，本机执行 frida-ps -U 应该能看到模拟器上启动的包名。&lt;/p&gt;
&lt;p&gt;然后启动一段python代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import frida, sys

jscode = &amp;quot;&amp;quot;&amp;quot;

Java.perform(function () {

var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig');

 SwitchConfig.isGlobalSpdySwitchOpen.overload().implementation = function(){

 var ret = this.isGlobalSpdySwitchOpen.apply(this, arguments);

 console.log(&amp;quot;isGlobalSpdySwitchOpenl &amp;quot;+ret)

 return false

 }

})

&amp;quot;&amp;quot;&amp;quot;

def on_message(message, data):

 if message['type'] == 'send':

 print(&amp;quot;[*] {0}&amp;quot;.format(message['payload']))

 else:

 print(message)

process = frida.get_usb_device().attach('fm.xiami.main')

script = process.create_script(jscode)

script.on('message', on_message)

script.load()

sys.stdin.read()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我们就可以测试抓包了。&lt;/p&gt;
&lt;p&gt;[frida-hook.py][][下载][frida-hook.py]&lt;/p&gt;
&lt;p&gt;[frida-server-14.0.8-android-arm64.xz]
[frida-server-14.0.8-android-arm64.xz 1][下载][frida-server-14.0.8-android-arm64.xz 1]&lt;/p&gt;</description></item><item><title>cocos creator 打包安卓黑屏</title><link>https://blog.wjhe.top/cocos-creator-%E6%89%93%E5%8C%85%E5%AE%89%E5%8D%93%E9%BB%91%E5%B1%8F/</link><pubDate>Wed, 16 Sep 2020 17:20:00 +0800</pubDate><guid>https://blog.wjhe.top/cocos-creator-%E6%89%93%E5%8C%85%E5%AE%89%E5%8D%93%E9%BB%91%E5%B1%8F/</guid><description>&lt;p&gt;这两天在写一个cocos项目，打包成安卓启动黑屏，用Android Studio 打包也是黑屏，报错是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2020-09-16 16:00:43.693 6525-6558/com.wangjian.hemusic E/jswrapper: ScriptEngine::evalString catch exception:
2020-09-16 16:00:43.702 6525-6558/com.wangjian.hemusic E/jswrapper: ERROR: Uncaught Error: Cannot find module 'index.js', location: assets/main/index.js:0:0
 STACK:
 [0]o@assets/main/index.js:11
 [1]o@assets/main/index.js:9
 [2]anonymous@assets/main/index.js:19
 [3]anonymous@jsb-adapter/jsb-engine.js:3322
 [4]download@jsb-adapter/jsb-engine.js:3333
 [5]downloadScript@jsb-adapter/jsb-engine.js:3321
 [6]anonymous@jsb-adapter/jsb-engine.js:3472
 [7]anonymous@jsb-adapter/jsb-engine.js:3149
 [8]readFile@jsb-adapter/jsb-engine.js:3116
 [9]readJson@jsb-adapter/jsb-engine.js:3137
 [10]parseJson@jsb-adapter/jsb-engine.js:3430
 [11]download@jsb-adapter/jsb-engine.js:3333
 [12]downloadJson@jsb-adapter/jsb-engine.js:3442
 [13]downloadBundle@jsb-adapter/jsb-engine.js:3464
 [14]a@src/cocos2d-jsb.js:10125
 [15]anonymous@src/cocos2d-jsb.js:10135
 [16]retry@src/cocos2d-jsb.js:11539
 [17]download@src/cocos2d-jsb.js:10120
 [18]load@src/cocos2d-jsb.js:10773
 [19]51.e.exports@src/cocos2d-jsb.js:105842020-09-16 16:00:43.702 6525-6558/com.wangjian.hemusic E/jswrapper: ScriptEngine::evalString script main.js, failed!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="image-1024x444.png" class="gallery-image" data-flex-basis="553px" data-flex-grow="230" height="533" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/cocos-creator-%E6%89%93%E5%8C%85%E5%AE%89%E5%8D%93%E9%BB%91%E5%B1%8F/image-1024x444.png" srcset="https://blog.wjhe.top/cocos-creator-%E6%89%93%E5%8C%85%E5%AE%89%E5%8D%93%E9%BB%91%E5%B1%8F/image-1024x444_hu_897a3bf3db37a1d4.png 800w, https://blog.wjhe.top/cocos-creator-%E6%89%93%E5%8C%85%E5%AE%89%E5%8D%93%E9%BB%91%E5%B1%8F/image-1024x444.png 1230w" width="1230"&gt;&lt;/p&gt;
&lt;p&gt;开始以为是creator的问题，重新打开其它项目构建编译就可以&lt;/p&gt;
&lt;p&gt;然后从代码上面入手，require有的地方我用的是相对路径，全部改为模块名字，还是黑屏&lt;/p&gt;
&lt;p&gt;百度了好久也找不到错误，然后以为是ts检查的问题，去掉 @ts-check，还是黑屏&lt;/p&gt;
&lt;p&gt;最后 发现 是因为Cocos Creator JS 这个插件 ，支持require跳转，帮我在一些自定义模块（不是cc.class）的顶部require了其它模块！！去掉它，还是老老实实在代码中require模块。&lt;/p&gt;
&lt;p&gt;启动就没有黑屏了&lt;/p&gt;</description></item><item><title>整理一些免费播放和下载付费音乐的网站</title><link>https://blog.wjhe.top/%E6%95%B4%E7%90%86%E4%B8%80%E4%BA%9B%E5%85%8D%E8%B4%B9%E4%B8%8B%E8%BD%BD%E4%BB%98%E8%B4%B9%E9%9F%B3%E4%B9%90%E7%9A%84%E7%BD%91%E7%AB%99/</link><pubDate>Mon, 20 Jul 2020 10:19:00 +0800</pubDate><guid>https://blog.wjhe.top/%E6%95%B4%E7%90%86%E4%B8%80%E4%BA%9B%E5%85%8D%E8%B4%B9%E4%B8%8B%E8%BD%BD%E4%BB%98%E8%B4%B9%E9%9F%B3%E4%B9%90%E7%9A%84%E7%BD%91%E7%AB%99/</guid><description>&lt;h2 id="the-sound-of-silence-已经凉了"&gt;&lt;del&gt;&lt;strong&gt;The Sound of Silence&lt;/strong&gt; （&lt;em&gt;可以下载无损&lt;/em&gt;）&lt;/del&gt; &lt;strong&gt;已经凉了&lt;/strong&gt;##
&lt;/h2&gt;&lt;p&gt;一个界面敲好看的，可以在线听歌和下载的网站，支持网易云、QQ音乐。&lt;/p&gt;
&lt;p&gt;地址是[http://music.jsososo.com/][http_music.jsososo.com]&lt;/p&gt;
&lt;h2 id="myfreemp3-已经凉了"&gt;&lt;del&gt;&lt;strong&gt;MyFreeMP3&lt;/strong&gt; （&lt;em&gt;可以下载无损&lt;/em&gt;）&lt;/del&gt; &lt;strong&gt;已经凉了&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;适配手机，支持收听和下载国内外大部分音乐平台的网站。&lt;/p&gt;
&lt;p&gt;地址是[http://tool.liumingye.cn/music/][http_tool.liumingye.cn_music]&lt;/p&gt;
&lt;h2 id="mmplayer-只能听不能下载-"&gt;&lt;strong&gt;mmPlayer&lt;/strong&gt; （&lt;em&gt;只能听，不能下载&lt;/em&gt; ）
&lt;/h2&gt;&lt;p&gt;一款支持网易云音乐收听和查看评论的网站。&lt;/p&gt;
&lt;p&gt;地址是[https://netease-music.fe-mm.com/][https_netease-music.fe-mm.com]&lt;/p&gt;
&lt;h2 id="搜你妹音乐网站-已经凉了"&gt;&lt;del&gt;&lt;strong&gt;搜你妹音乐网站&lt;/strong&gt;（&lt;em&gt;可以下载无损&lt;/em&gt;）&lt;/del&gt; &lt;strong&gt;已经凉了&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;适配手机，支持国内五大音乐平台，QQ音乐支持付费专辑下载。&lt;/p&gt;
&lt;p&gt;[https://wsmusic.sounm.com/][https_wsmusic.sounm.com]&lt;/p&gt;
&lt;h2 id="果壳音乐搜索-已经凉了"&gt;&lt;del&gt;&lt;strong&gt;果壳音乐搜索&lt;/strong&gt;（&lt;em&gt;可以下载无损&lt;/em&gt;）&lt;/del&gt; &lt;strong&gt;已经凉了&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;支持酷我、网易云、QQ搜索和下载，QQ音乐也支持付费专辑下载。&lt;/p&gt;
&lt;p&gt;[http://music.ghpym.com/][http_music.ghpym.com]&lt;/p&gt;
&lt;h2 id="下歌吧音乐下载平台可以下载无损"&gt;&lt;strong&gt;下歌吧音乐下载平台&lt;/strong&gt;（&lt;em&gt;可以下载无损&lt;/em&gt;）
&lt;/h2&gt;&lt;p&gt;线路A：网易云音乐&lt;br&gt;
线路B：酷我音乐&lt;br&gt;
线路C：咪咕音乐&lt;/p&gt;
&lt;p&gt;&lt;del&gt;[http://music.y444.cn/][http_music.y444.cn]&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;[https://xiageba.com/][https_xiageba.com]&lt;/p&gt;
&lt;h2 id="无损生活"&gt;&lt;strong&gt;无损生活&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;[https://www.flac.life/][https://www.flac.life/]&lt;/p&gt;
&lt;h2 id="musicbox"&gt;&lt;strong&gt;musicBox&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;项目开源地址：https://github.com/xfmujie/musicBox&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;笒鬼鬼音乐盒：[https://cenguigui.cn/music/][https://cenguigui.cn/music/]&lt;/li&gt;
&lt;li&gt;昔枫音乐盒：[https://mu-jie.cc/musicBox/][https://mu-jie.cc/musicBox/]&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gd音乐台"&gt;&lt;strong&gt;GD音乐台&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://music.gdstudio.xyz/" target="_blank" rel="noopener"
 &gt;https://music.gdstudio.xyz/&lt;/a&gt;&lt;/p&gt;</description></item><item><title>nginx 代理 golang，禁止访问目录的办法</title><link>https://blog.wjhe.top/nginx-%E4%BB%A3%E7%90%86-golang%E7%A6%81%E6%AD%A2%E8%AE%BF%E9%97%AE%E7%9B%AE%E5%BD%95%E7%9A%84%E5%8A%9E%E6%B3%95/</link><pubDate>Thu, 16 Jul 2020 19:03:50 +0800</pubDate><guid>https://blog.wjhe.top/nginx-%E4%BB%A3%E7%90%86-golang%E7%A6%81%E6%AD%A2%E8%AE%BF%E9%97%AE%E7%9B%AE%E5%BD%95%E7%9A%84%E5%8A%9E%E6%B3%95/</guid><description>&lt;p&gt;项目是用golang作为服务器，nginx负责反向代理。&lt;/p&gt;
&lt;p&gt;但是现在有一个问题，golang的web服务器，目录浏览不能自定义关闭。&lt;/p&gt;
&lt;p&gt;最初是想nginx代理静态文件，然后后台请求反向代理到golang，但是这样有更新每次都要git pull两次，有点麻烦。最后还是选择向nginx下手。目录浏览的路径最后肯定是 / ,然后除去根目录， 其它文件夹肯定是/xxx/这样的，直接一个通配符就好了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location ~ (/.*/)${ return 404;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目前够用了。&lt;/p&gt;</description></item></channel></rss>